@tradejs/node 3.1.29-beta.290 → 3.1.29-beta.292
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.js +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2594 -1904
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-PB2KHBGH.mjs → chunk-EVKQ4DI6.mjs} +461 -33
- package/dist/chunk-NJEYNGRU.mjs +226 -0
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +834 -773
- package/dist/cli.mjs +1 -1
- package/dist/diagnostics.js +2611 -1921
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2634 -1944
- package/dist/mcp.mjs +4 -3
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +243 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2614 -1924
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2335 -1643
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2615 -1925
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2153 -1463
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/backtest.js
CHANGED
|
@@ -795,1647 +795,2425 @@ module.exports = __toCommonJS(backtest_exports);
|
|
|
795
795
|
__reExport(backtest_exports, require("@tradejs/core/backtest"), module.exports);
|
|
796
796
|
|
|
797
797
|
// src/testing.ts
|
|
798
|
-
var
|
|
798
|
+
var import_indicators4 = require("@tradejs/core/indicators");
|
|
799
799
|
var import_constants9 = require("@tradejs/core/constants");
|
|
800
800
|
var import_strategies7 = require("@tradejs/core/strategies");
|
|
801
801
|
var import_time3 = require("@tradejs/core/time");
|
|
802
802
|
var import_logger15 = require("@tradejs/infra/logger");
|
|
803
803
|
|
|
804
|
-
// src/
|
|
805
|
-
var
|
|
804
|
+
// src/strategy/portfolioRuntime.ts
|
|
805
|
+
var import_node_crypto2 = require("crypto");
|
|
806
|
+
var import_portfolio2 = require("@tradejs/core/portfolio");
|
|
807
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
806
808
|
|
|
807
|
-
// src/
|
|
808
|
-
var
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const questions = questionsForJevInput(input);
|
|
839
|
-
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
840
|
-
const base = {
|
|
841
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
842
|
-
schema: "tradejs-signal-assessment/v5",
|
|
843
|
-
source: config.source,
|
|
844
|
-
mode: config.mode,
|
|
845
|
-
inputHash,
|
|
846
|
-
questionsHash,
|
|
847
|
-
geometryStatus: input.geometryStatus,
|
|
848
|
-
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
849
|
-
};
|
|
850
|
-
if (!input.questions.length)
|
|
851
|
-
return {
|
|
852
|
-
assessment: {
|
|
853
|
-
...base,
|
|
854
|
-
status: "unavailable",
|
|
855
|
-
model: "",
|
|
856
|
-
scores: emptyScores(),
|
|
857
|
-
confidence: emptyScores()
|
|
858
|
-
}
|
|
859
|
-
};
|
|
860
|
-
if (config.source === "local") {
|
|
861
|
-
const contents = await import_promises.default.readFile(
|
|
862
|
-
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
863
|
-
"utf8"
|
|
864
|
-
);
|
|
865
|
-
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
866
|
-
throw new Error("Local Jev gate checksum mismatch");
|
|
867
|
-
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
868
|
-
const model = validateJevGateModel2(JSON.parse(contents));
|
|
869
|
-
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
870
|
-
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
871
|
-
const scores = Object.fromEntries(
|
|
872
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
873
|
-
key,
|
|
874
|
-
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
875
|
-
])
|
|
876
|
-
);
|
|
877
|
-
return {
|
|
878
|
-
assessment: {
|
|
879
|
-
...base,
|
|
880
|
-
status: "available",
|
|
881
|
-
model: config.modelSha256,
|
|
882
|
-
scores,
|
|
883
|
-
confidence: emptyScores()
|
|
809
|
+
// src/portfolio.ts
|
|
810
|
+
var import_portfolio = require("@tradejs/core/portfolio");
|
|
811
|
+
function createPortfolioCoordinator({
|
|
812
|
+
policy,
|
|
813
|
+
strategy,
|
|
814
|
+
store,
|
|
815
|
+
execution
|
|
816
|
+
}) {
|
|
817
|
+
const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
|
|
818
|
+
let cache;
|
|
819
|
+
async function transact(now2, change, force = false) {
|
|
820
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
821
|
+
const previous = await store.read();
|
|
822
|
+
if (previous && previous.fingerprint !== fingerprint3)
|
|
823
|
+
throw new Error(
|
|
824
|
+
"Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
|
|
825
|
+
);
|
|
826
|
+
const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
827
|
+
const bucket = Math.floor(now2 / policy.observationIntervalMs);
|
|
828
|
+
const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
|
|
829
|
+
const reconciled = (0, import_portfolio.reconcilePortfolio)(
|
|
830
|
+
policy,
|
|
831
|
+
strategy,
|
|
832
|
+
base,
|
|
833
|
+
observation
|
|
834
|
+
);
|
|
835
|
+
for (const intent of base.closing) {
|
|
836
|
+
if (!observation.positions.some(
|
|
837
|
+
(p) => p.symbol === intent.position.symbol
|
|
838
|
+
))
|
|
839
|
+
await execution.confirmClosed?.(intent, observation.asOf);
|
|
884
840
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
891
|
-
const getRecord = async () => {
|
|
892
|
-
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
893
|
-
if (existing) {
|
|
894
|
-
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))
|
|
895
|
-
throw new Error("Jev recording identity mismatch");
|
|
896
|
-
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
897
|
-
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
898
|
-
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
899
|
-
if (existing.scores[key] !== expected)
|
|
900
|
-
throw new Error("Jev recording scores do not match the response");
|
|
841
|
+
const changed = change(reconciled, observation);
|
|
842
|
+
const next = { ...changed.state, revision: base.revision + 1 };
|
|
843
|
+
if (await store.compare(previous, next)) {
|
|
844
|
+
cache = { revision: next.revision, bucket, observation };
|
|
845
|
+
return { state: next, observation, result: changed.result };
|
|
901
846
|
}
|
|
902
|
-
|
|
847
|
+
cache = void 0;
|
|
903
848
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
849
|
+
throw new Error("Portfolio state contention; entry was not submitted");
|
|
850
|
+
}
|
|
851
|
+
async function tick(now2, force = false) {
|
|
852
|
+
const claimed = await transact(
|
|
853
|
+
now2,
|
|
854
|
+
(state, observation) => {
|
|
855
|
+
const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
|
|
856
|
+
return {
|
|
857
|
+
state: {
|
|
858
|
+
...state,
|
|
859
|
+
closing: state.closing.map(
|
|
860
|
+
(c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
|
|
861
|
+
)
|
|
862
|
+
},
|
|
863
|
+
result: commands
|
|
864
|
+
};
|
|
865
|
+
},
|
|
866
|
+
force
|
|
867
|
+
);
|
|
868
|
+
for (const command of claimed.result)
|
|
869
|
+
await execution.close(
|
|
870
|
+
command.position,
|
|
871
|
+
command.intent,
|
|
872
|
+
claimed.observation.asOf
|
|
907
873
|
);
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
874
|
+
if (claimed.result.length) {
|
|
875
|
+
cache = void 0;
|
|
876
|
+
const refreshed = await transact(
|
|
877
|
+
now2,
|
|
878
|
+
(state) => ({ state, result: void 0 }),
|
|
879
|
+
true
|
|
880
|
+
);
|
|
881
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
882
|
+
policy,
|
|
883
|
+
strategy,
|
|
884
|
+
refreshed.state,
|
|
885
|
+
refreshed.observation
|
|
916
886
|
);
|
|
917
|
-
const started = Date.now();
|
|
918
|
-
const state = stateForJevInput(input);
|
|
919
|
-
const responseId = (0, import_jev5.jevHash)({
|
|
920
|
-
state,
|
|
921
|
-
questionsHash,
|
|
922
|
-
provider,
|
|
923
|
-
user: (0, import_jev5.jevHash)(userName)
|
|
924
|
-
});
|
|
925
|
-
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
926
|
-
let responsePromise = pendingResponses.get(responseFile);
|
|
927
|
-
if (!responsePromise) {
|
|
928
|
-
responsePromise = (async () => {
|
|
929
|
-
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
930
|
-
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
931
|
-
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
932
|
-
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
933
|
-
return fresh;
|
|
934
|
-
})();
|
|
935
|
-
pendingResponses.set(responseFile, responsePromise);
|
|
936
|
-
}
|
|
937
|
-
let response;
|
|
938
|
-
try {
|
|
939
|
-
response = await responsePromise;
|
|
940
|
-
} finally {
|
|
941
|
-
pendingResponses.delete(responseFile);
|
|
942
887
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
888
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
889
|
+
policy,
|
|
890
|
+
strategy,
|
|
891
|
+
claimed.state,
|
|
892
|
+
claimed.observation
|
|
948
893
|
);
|
|
949
|
-
const value = {
|
|
950
|
-
schema: "tradejs-jev-record/v4",
|
|
951
|
-
id,
|
|
952
|
-
inputHash,
|
|
953
|
-
questionsHash,
|
|
954
|
-
provider,
|
|
955
|
-
input,
|
|
956
|
-
response,
|
|
957
|
-
scores,
|
|
958
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
959
|
-
elapsedMs: Date.now() - started
|
|
960
|
-
};
|
|
961
|
-
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
962
|
-
return await (0, import_jev5.readJevArtifact)(file);
|
|
963
|
-
};
|
|
964
|
-
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
965
|
-
let promise = pending.get(pendingKey);
|
|
966
|
-
if (!promise) {
|
|
967
|
-
promise = getRecord();
|
|
968
|
-
pending.set(pendingKey, promise);
|
|
969
894
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
895
|
+
async function updateReservation(now2, id, submitted, qty) {
|
|
896
|
+
return transact(
|
|
897
|
+
now2,
|
|
898
|
+
(s, o) => {
|
|
899
|
+
const exists = s.reservations.some((r) => r.id === id);
|
|
900
|
+
const blocked = submitted && (!exists || s.closing.length > 0 || o.asOf <= s.blockedThrough);
|
|
901
|
+
return {
|
|
902
|
+
state: {
|
|
903
|
+
...s,
|
|
904
|
+
reservations: s.reservations.flatMap(
|
|
905
|
+
(r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
|
|
906
|
+
)
|
|
907
|
+
},
|
|
908
|
+
result: !blocked
|
|
909
|
+
};
|
|
910
|
+
},
|
|
911
|
+
true
|
|
912
|
+
);
|
|
975
913
|
}
|
|
976
914
|
return {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
915
|
+
async context(now2) {
|
|
916
|
+
const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
917
|
+
if (state.fingerprint !== fingerprint3)
|
|
918
|
+
throw new Error("Portfolio policy changed under existing state");
|
|
919
|
+
const observation = await execution.observe(now2, state.reservations);
|
|
920
|
+
(0, import_portfolio.validatePortfolioObservation)(observation);
|
|
921
|
+
return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
|
|
922
|
+
},
|
|
923
|
+
tick,
|
|
924
|
+
async submit(now2, entry, send) {
|
|
925
|
+
await tick(now2);
|
|
926
|
+
const admitted = await transact(
|
|
927
|
+
now2,
|
|
928
|
+
(s, o) => {
|
|
929
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
930
|
+
...entry,
|
|
931
|
+
submitted: false,
|
|
932
|
+
oppositeHandled: false
|
|
933
|
+
});
|
|
934
|
+
return { state: r.state, result: r.reason };
|
|
935
|
+
},
|
|
936
|
+
true
|
|
937
|
+
);
|
|
938
|
+
if (admitted.result) return { reason: admitted.result };
|
|
939
|
+
let submitted = false;
|
|
940
|
+
let retry = 0;
|
|
941
|
+
let attemptId = entry.id;
|
|
942
|
+
let needsReservation = false;
|
|
943
|
+
try {
|
|
944
|
+
const value = await send(
|
|
945
|
+
async (qty) => {
|
|
946
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
947
|
+
throw new Error("Invalid submitted portfolio quantity");
|
|
948
|
+
if (needsReservation) {
|
|
949
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
950
|
+
const admission = await transact(
|
|
951
|
+
now2,
|
|
952
|
+
(s, o) => {
|
|
953
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
954
|
+
...entry,
|
|
955
|
+
id: attemptId,
|
|
956
|
+
qty,
|
|
957
|
+
submitted: true,
|
|
958
|
+
oppositeHandled: false
|
|
959
|
+
});
|
|
960
|
+
return { state: r.state, result: r.reason };
|
|
961
|
+
},
|
|
962
|
+
true
|
|
963
|
+
);
|
|
964
|
+
if (admission.result) throw new Error(admission.result);
|
|
965
|
+
needsReservation = false;
|
|
966
|
+
submitted = true;
|
|
967
|
+
return attemptId;
|
|
968
|
+
}
|
|
969
|
+
const update = await updateReservation(now2, attemptId, true, qty);
|
|
970
|
+
if (!update.result)
|
|
971
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
972
|
+
submitted = true;
|
|
973
|
+
return attemptId;
|
|
974
|
+
},
|
|
975
|
+
async (retrying = false) => {
|
|
976
|
+
await transact(
|
|
977
|
+
now2,
|
|
978
|
+
(s) => ({
|
|
979
|
+
state: {
|
|
980
|
+
...s,
|
|
981
|
+
reservations: s.reservations.filter(
|
|
982
|
+
(r) => r.id !== attemptId
|
|
983
|
+
),
|
|
984
|
+
completedSignals: {
|
|
985
|
+
...Object.fromEntries(
|
|
986
|
+
Object.entries(s.completedSignals).filter(
|
|
987
|
+
([id]) => !retrying || id !== entry.signalId
|
|
988
|
+
)
|
|
989
|
+
),
|
|
990
|
+
...retrying ? {} : { [entry.signalId]: now2 }
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
result: void 0
|
|
994
|
+
}),
|
|
995
|
+
true
|
|
996
|
+
);
|
|
997
|
+
submitted = false;
|
|
998
|
+
needsReservation = retrying;
|
|
999
|
+
}
|
|
1000
|
+
);
|
|
1001
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
1002
|
+
else await tick(now2, true);
|
|
1003
|
+
return { value };
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
1006
|
+
throw error;
|
|
1007
|
+
}
|
|
990
1008
|
}
|
|
991
1009
|
};
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
try {
|
|
1003
|
-
const input = buildJevInput(signal);
|
|
1004
|
-
({ assessment } = await evaluateJevInput({
|
|
1005
|
-
input,
|
|
1006
|
-
config,
|
|
1007
|
-
userName,
|
|
1008
|
-
projectRoot
|
|
1009
|
-
}));
|
|
1010
|
-
} catch (error) {
|
|
1011
|
-
if (strict) throw error;
|
|
1012
|
-
assessment = {
|
|
1013
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
1014
|
-
schema: "tradejs-signal-assessment/v5",
|
|
1015
|
-
source: config.source,
|
|
1016
|
-
mode: config.mode,
|
|
1017
|
-
status: "unavailable",
|
|
1018
|
-
inputHash: "",
|
|
1019
|
-
questionsHash: "",
|
|
1020
|
-
geometryStatus: "absent",
|
|
1021
|
-
levelsValid: null,
|
|
1022
|
-
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
1023
|
-
scores: emptyScores(),
|
|
1024
|
-
confidence: emptyScores()
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
signal.assessment = assessment;
|
|
1028
|
-
signal.additionalIndicators = {
|
|
1029
|
-
...signal.additionalIndicators,
|
|
1030
|
-
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
1010
|
+
}
|
|
1011
|
+
function createMemoryPortfolioStore() {
|
|
1012
|
+
let state = null;
|
|
1013
|
+
return {
|
|
1014
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
1015
|
+
compare: async (expected, next) => {
|
|
1016
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
1017
|
+
state = structuredClone(next);
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1031
1020
|
};
|
|
1032
|
-
|
|
1033
|
-
signalId: signal.signalId,
|
|
1034
|
-
strategy: signal.strategy,
|
|
1035
|
-
symbol: signal.symbol,
|
|
1036
|
-
timestamp: signal.timestamp,
|
|
1037
|
-
scope,
|
|
1038
|
-
user: (0, import_jev5.jevHash)(userName),
|
|
1039
|
-
config,
|
|
1040
|
-
assessment
|
|
1041
|
-
};
|
|
1042
|
-
await (0, import_jev5.writeJevArtifact)(
|
|
1043
|
-
import_node_path.default.resolve(
|
|
1044
|
-
projectRoot,
|
|
1045
|
-
config.recordsDir ?? "data/ai/jev",
|
|
1046
|
-
"decisions",
|
|
1047
|
-
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
1048
|
-
),
|
|
1049
|
-
identity
|
|
1050
|
-
);
|
|
1051
|
-
return assessment;
|
|
1052
|
-
};
|
|
1053
|
-
|
|
1054
|
-
// src/strategyRuntime.ts
|
|
1055
|
-
var import_constants5 = require("@tradejs/core/constants");
|
|
1056
|
-
var import_strategies6 = require("@tradejs/core/strategies");
|
|
1057
|
-
var import_logger11 = require("@tradejs/infra/logger");
|
|
1058
|
-
|
|
1059
|
-
// src/strategyHelpers/runtime.ts
|
|
1060
|
-
var import_logger9 = require("@tradejs/infra/logger");
|
|
1061
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
1062
|
-
var import_ml2 = require("@tradejs/infra/ml");
|
|
1021
|
+
}
|
|
1063
1022
|
|
|
1064
|
-
// src/
|
|
1023
|
+
// src/runtimeJournal.ts
|
|
1024
|
+
var import_node_crypto = require("crypto");
|
|
1025
|
+
var import_constants = require("@tradejs/core/constants");
|
|
1026
|
+
var import_time = require("@tradejs/core/time");
|
|
1027
|
+
var import_trade = require("@tradejs/core/trade");
|
|
1065
1028
|
var import_logger = require("@tradejs/infra/logger");
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
var
|
|
1069
|
-
|
|
1070
|
-
super(...arguments);
|
|
1071
|
-
this.name = "EntrySubmissionValidationError";
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
1074
|
-
var resolveEntryExecutionLimits = (config, direction) => {
|
|
1075
|
-
const side = config[direction];
|
|
1076
|
-
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
1077
|
-
const maximum = config.MAX_LOSS_VALUE;
|
|
1078
|
-
if (minimum == null && maximum == null) return void 0;
|
|
1079
|
-
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
1080
|
-
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
1081
|
-
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
1082
|
-
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
1083
|
-
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
1084
|
-
const slippageBps = slippage + impact;
|
|
1085
|
-
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)
|
|
1086
|
-
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
1087
|
-
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
1088
|
-
};
|
|
1089
|
-
var validateEntryExecution = ({
|
|
1029
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
1030
|
+
var now = () => Date.now();
|
|
1031
|
+
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
1032
|
+
var calculateClosedPnl = ({
|
|
1090
1033
|
direction,
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
takeProfitPrice,
|
|
1095
|
-
limits
|
|
1034
|
+
entryPrice,
|
|
1035
|
+
exitPrice,
|
|
1036
|
+
qty
|
|
1096
1037
|
}) => {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1100
|
-
);
|
|
1101
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
1102
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
1103
|
-
throw new EntrySubmissionValidationError(
|
|
1104
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
1105
|
-
);
|
|
1106
|
-
const slippage = limits.slippageBps / 1e4;
|
|
1107
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
1108
|
-
const entryCost = cost(price, side);
|
|
1109
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
1110
|
-
const rewardPerUnit = Math.max(
|
|
1111
|
-
0,
|
|
1112
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
1113
|
-
);
|
|
1114
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
1115
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
1116
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
1117
|
-
throw new EntrySubmissionValidationError(
|
|
1118
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1119
|
-
);
|
|
1120
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
1121
|
-
throw new EntrySubmissionValidationError(
|
|
1122
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
1123
|
-
);
|
|
1124
|
-
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
1125
|
-
return { netRiskRatio, plannedLossValue, riskExceeded };
|
|
1038
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
1039
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
1126
1040
|
};
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1136
|
-
if (
|
|
1137
|
-
return
|
|
1138
|
-
arrivalSnapshotTime: Date.now(),
|
|
1139
|
-
arrivalSource: "unavailable",
|
|
1140
|
-
bid: null,
|
|
1141
|
-
ask: null,
|
|
1142
|
-
arrivalMid: null,
|
|
1143
|
-
spreadBps: null
|
|
1144
|
-
};
|
|
1041
|
+
var createRuntimeOrderId = (strategy) => {
|
|
1042
|
+
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
1043
|
+
if (prefix === "tjs-") {
|
|
1044
|
+
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
1045
|
+
}
|
|
1046
|
+
return `${prefix}${toRandomOrderSuffix()}`;
|
|
1047
|
+
};
|
|
1048
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
1049
|
+
const { userName } = params;
|
|
1050
|
+
if (!userName) {
|
|
1051
|
+
return null;
|
|
1145
1052
|
}
|
|
1053
|
+
const record = {
|
|
1054
|
+
...params,
|
|
1055
|
+
entryCount: 1,
|
|
1056
|
+
lastEntryPrice: params.entryPrice,
|
|
1057
|
+
lastEntryQty: params.qty,
|
|
1058
|
+
lastEntryTimestamp: params.entryTimestamp,
|
|
1059
|
+
status: "active",
|
|
1060
|
+
currentPrice: params.entryPrice,
|
|
1061
|
+
currentPnl: 0,
|
|
1062
|
+
closedPnl: null,
|
|
1063
|
+
exitPrice: null,
|
|
1064
|
+
exitTimestamp: null,
|
|
1065
|
+
lastSyncedAt: now()
|
|
1066
|
+
};
|
|
1067
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
1068
|
+
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
1146
1069
|
try {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1070
|
+
await Promise.all([
|
|
1071
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
1072
|
+
expire: 0
|
|
1073
|
+
}),
|
|
1074
|
+
(0, import_redis.setHashJsonField)(
|
|
1075
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1076
|
+
record.orderId,
|
|
1077
|
+
record,
|
|
1078
|
+
{ expire: 0 }
|
|
1079
|
+
),
|
|
1080
|
+
(0, import_redis.setData)(
|
|
1081
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
1082
|
+
{ orderId: record.orderId },
|
|
1083
|
+
{ expire: 0 }
|
|
1084
|
+
)
|
|
1085
|
+
]);
|
|
1162
1086
|
} catch (error) {
|
|
1163
|
-
import_logger.logger.
|
|
1164
|
-
"runtime
|
|
1165
|
-
symbol,
|
|
1087
|
+
import_logger.logger.error(
|
|
1088
|
+
"runtime trade open journal failed: %s %s",
|
|
1089
|
+
record.symbol,
|
|
1166
1090
|
error?.message || String(error)
|
|
1167
1091
|
);
|
|
1168
|
-
return {
|
|
1169
|
-
arrivalSnapshotTime: Date.now(),
|
|
1170
|
-
arrivalSource: "top_of_book_error",
|
|
1171
|
-
bid: null,
|
|
1172
|
-
ask: null,
|
|
1173
|
-
arrivalMid: null,
|
|
1174
|
-
spreadBps: null
|
|
1175
|
-
};
|
|
1176
1092
|
}
|
|
1093
|
+
return record;
|
|
1177
1094
|
};
|
|
1178
|
-
var
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
}
|
|
1095
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
1096
|
+
const {
|
|
1097
|
+
userName,
|
|
1098
|
+
strategy,
|
|
1099
|
+
symbol,
|
|
1100
|
+
direction,
|
|
1101
|
+
resultingQty,
|
|
1102
|
+
resultingEntryPrice,
|
|
1103
|
+
addedQty,
|
|
1104
|
+
addedEntryPrice,
|
|
1105
|
+
entryTimestamp,
|
|
1106
|
+
fee,
|
|
1107
|
+
accountId,
|
|
1108
|
+
deploymentId
|
|
1109
|
+
} = params;
|
|
1110
|
+
if (!userName) {
|
|
1111
|
+
return null;
|
|
1196
1112
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1113
|
+
const existing = await getActiveRuntimeTrade({
|
|
1114
|
+
userName,
|
|
1115
|
+
symbol,
|
|
1116
|
+
accountId,
|
|
1117
|
+
deploymentId
|
|
1118
|
+
});
|
|
1119
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
1120
|
+
return null;
|
|
1199
1121
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
arrivalSnapshotTime: Date.now(),
|
|
1219
|
-
arrivalSource: "simulation",
|
|
1220
|
-
bid: executionPrice,
|
|
1221
|
-
ask: executionPrice,
|
|
1222
|
-
arrivalMid: executionPrice,
|
|
1223
|
-
spreadBps: 0
|
|
1224
|
-
} : await getOrderArrivalSnapshot({
|
|
1225
|
-
connector,
|
|
1226
|
-
symbol,
|
|
1227
|
-
requireTimestamp: true
|
|
1228
|
-
});
|
|
1229
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
1230
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
1231
|
-
throw new EntrySubmissionValidationError(
|
|
1232
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
1233
|
-
);
|
|
1234
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
1235
|
-
throw new EntrySubmissionValidationError(
|
|
1236
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
1237
|
-
);
|
|
1238
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
1239
|
-
validateEntryProtectionAtArrival({
|
|
1240
|
-
direction,
|
|
1241
|
-
signalPrice: price,
|
|
1242
|
-
bid: snapshot2.bid,
|
|
1243
|
-
ask: snapshot2.ask,
|
|
1244
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
1245
|
-
takeProfits,
|
|
1246
|
-
stopLossPrice
|
|
1247
|
-
});
|
|
1248
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
1249
|
-
const economics2 = validateEntryExecution({
|
|
1250
|
-
direction,
|
|
1251
|
-
price,
|
|
1252
|
-
qty,
|
|
1253
|
-
stopLossPrice,
|
|
1254
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
1255
|
-
limits
|
|
1256
|
-
});
|
|
1257
|
-
onValidated?.(snapshot2, economics2);
|
|
1122
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
1123
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
1124
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
1125
|
+
const next = {
|
|
1126
|
+
...existing,
|
|
1127
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
1128
|
+
qty: resultingQty,
|
|
1129
|
+
entryPrice: resultingEntryPrice,
|
|
1130
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
1131
|
+
lastEntryPrice: addedEntryPrice,
|
|
1132
|
+
lastEntryQty: addedQty,
|
|
1133
|
+
lastEntryTimestamp: entryTimestamp,
|
|
1134
|
+
currentPrice: resultingEntryPrice,
|
|
1135
|
+
currentPnl: 0,
|
|
1136
|
+
fee: openFee,
|
|
1137
|
+
openFee,
|
|
1138
|
+
totalFee,
|
|
1139
|
+
lastSyncedAt: now()
|
|
1258
1140
|
};
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
return trimmed.map(
|
|
1279
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
1280
|
-
);
|
|
1281
|
-
}
|
|
1282
|
-
if (value && typeof value === "object") {
|
|
1283
|
-
return Object.fromEntries(
|
|
1284
|
-
Object.entries(value).map(([key, nested]) => [
|
|
1285
|
-
key,
|
|
1286
|
-
trimSeriesDeep(nested)
|
|
1287
|
-
])
|
|
1141
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1142
|
+
try {
|
|
1143
|
+
await Promise.all([
|
|
1144
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
1145
|
+
expire: 0
|
|
1146
|
+
}),
|
|
1147
|
+
(0, import_redis.setHashJsonField)(
|
|
1148
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1149
|
+
existing.orderId,
|
|
1150
|
+
next,
|
|
1151
|
+
{ expire: 0 }
|
|
1152
|
+
)
|
|
1153
|
+
]);
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
import_logger.logger.error(
|
|
1156
|
+
"runtime trade increase journal failed: %s %s",
|
|
1157
|
+
symbol,
|
|
1158
|
+
error?.message || String(error)
|
|
1288
1159
|
);
|
|
1289
1160
|
}
|
|
1290
|
-
return
|
|
1161
|
+
return next;
|
|
1291
1162
|
};
|
|
1292
|
-
var
|
|
1293
|
-
const
|
|
1294
|
-
if (
|
|
1295
|
-
return
|
|
1163
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
1164
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
1165
|
+
if (!userName) {
|
|
1166
|
+
return null;
|
|
1296
1167
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
if (!
|
|
1168
|
+
const activeRef = await (0, import_redis.getData)(
|
|
1169
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
1170
|
+
null
|
|
1171
|
+
);
|
|
1172
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
1173
|
+
if (!orderId) {
|
|
1303
1174
|
return null;
|
|
1304
1175
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
};
|
|
1315
|
-
var buildMissingSpreadContext = () => ({
|
|
1316
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1317
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1318
|
-
available: false,
|
|
1319
|
-
value: null,
|
|
1320
|
-
zScore: null,
|
|
1321
|
-
bps: null,
|
|
1322
|
-
absBps: null,
|
|
1323
|
-
bias: null,
|
|
1324
|
-
severity: null
|
|
1325
|
-
});
|
|
1326
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
1327
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1328
|
-
const relative = toRecord(baseContext?.relative);
|
|
1329
|
-
const execution = toRecord(relative?.execution);
|
|
1330
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
1331
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
1332
|
-
if (spread == null) {
|
|
1333
|
-
return buildMissingSpreadContext();
|
|
1176
|
+
const existing = await (0, import_redis.getData)(
|
|
1177
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1178
|
+
null
|
|
1179
|
+
);
|
|
1180
|
+
if (!existing || existing.status !== "active") {
|
|
1181
|
+
await (0, import_redis.delKey)(
|
|
1182
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
1183
|
+
);
|
|
1184
|
+
return null;
|
|
1334
1185
|
}
|
|
1335
|
-
|
|
1336
|
-
const bps = roundTo(value * 1e4, 2);
|
|
1337
|
-
const absBps = Math.abs(bps);
|
|
1338
|
-
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
1339
|
-
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
1340
|
-
return {
|
|
1341
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1342
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1343
|
-
available: true,
|
|
1344
|
-
value,
|
|
1345
|
-
zScore,
|
|
1346
|
-
bps,
|
|
1347
|
-
absBps,
|
|
1348
|
-
bias,
|
|
1349
|
-
severity
|
|
1350
|
-
};
|
|
1186
|
+
return existing;
|
|
1351
1187
|
};
|
|
1352
|
-
var
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
signedVolumeZScore: null
|
|
1368
|
-
};
|
|
1188
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
1189
|
+
const {
|
|
1190
|
+
userName,
|
|
1191
|
+
expectedOrderId,
|
|
1192
|
+
symbol,
|
|
1193
|
+
strategy,
|
|
1194
|
+
exitPrice,
|
|
1195
|
+
exitTimestamp,
|
|
1196
|
+
closedPnl,
|
|
1197
|
+
exitType,
|
|
1198
|
+
accountId,
|
|
1199
|
+
deploymentId
|
|
1200
|
+
} = params;
|
|
1201
|
+
if (!userName) {
|
|
1202
|
+
return null;
|
|
1369
1203
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
};
|
|
1381
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
1382
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1383
|
-
const participation = toRecord(baseContext?.participation);
|
|
1384
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
1385
|
-
if (!tradeFlow) {
|
|
1386
|
-
return {
|
|
1387
|
-
source: null,
|
|
1388
|
-
available: false,
|
|
1389
|
-
interval: null,
|
|
1390
|
-
stale: null,
|
|
1391
|
-
trades: null,
|
|
1392
|
-
buyPressurePct: null,
|
|
1393
|
-
netBaseDelta: null,
|
|
1394
|
-
netQuoteDelta: null
|
|
1395
|
-
};
|
|
1204
|
+
const existing = expectedOrderId ? await (0, import_redis.getDataStrict)(
|
|
1205
|
+
import_redis.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
1206
|
+
) : await getActiveRuntimeTrade({
|
|
1207
|
+
userName,
|
|
1208
|
+
symbol,
|
|
1209
|
+
accountId,
|
|
1210
|
+
deploymentId
|
|
1211
|
+
});
|
|
1212
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
1213
|
+
return null;
|
|
1396
1214
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1215
|
+
const orderId = existing.orderId;
|
|
1216
|
+
if (strategy && existing.strategy !== strategy) {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
1220
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
1221
|
+
direction: existing.direction,
|
|
1222
|
+
entryPrice: existing.entryPrice,
|
|
1223
|
+
exitPrice: resolvedExitPrice,
|
|
1224
|
+
qty: existing.qty
|
|
1225
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
1226
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
1227
|
+
...existing,
|
|
1228
|
+
status: "closed",
|
|
1229
|
+
currentPrice: resolvedExitPrice,
|
|
1230
|
+
currentPnl: resolvedClosedPnl,
|
|
1231
|
+
closedPnl: resolvedClosedPnl,
|
|
1232
|
+
exitPrice: resolvedExitPrice,
|
|
1233
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
1234
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
1235
|
+
lastSyncedAt: now()
|
|
1406
1236
|
};
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1237
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1238
|
+
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
1239
|
+
try {
|
|
1240
|
+
await Promise.all([
|
|
1241
|
+
(expectedOrderId ? import_redis.setDataStrict : import_redis.setData)(
|
|
1242
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1243
|
+
next,
|
|
1244
|
+
{
|
|
1245
|
+
expire: import_constants.TTL_1M
|
|
1246
|
+
}
|
|
1247
|
+
),
|
|
1248
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1249
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1250
|
+
orderId,
|
|
1251
|
+
next,
|
|
1252
|
+
{ expire: import_constants.TTL_1M }
|
|
1253
|
+
),
|
|
1254
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1255
|
+
import_redis.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
1256
|
+
orderId,
|
|
1257
|
+
next,
|
|
1258
|
+
{ expire: import_constants.TTL_1M }
|
|
1259
|
+
),
|
|
1260
|
+
expectedOrderId ? (0, import_redis.deleteRuntimeTradeReference)(
|
|
1261
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1262
|
+
userName,
|
|
1263
|
+
symbol,
|
|
1264
|
+
deploymentId ?? accountId
|
|
1265
|
+
),
|
|
1266
|
+
expectedOrderId
|
|
1267
|
+
) : (0, import_redis.delKey)(
|
|
1268
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1269
|
+
userName,
|
|
1270
|
+
symbol,
|
|
1271
|
+
deploymentId ?? accountId
|
|
1272
|
+
)
|
|
1273
|
+
)
|
|
1274
|
+
]);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
if (expectedOrderId) throw error;
|
|
1277
|
+
import_logger.logger.error(
|
|
1278
|
+
"runtime trade close journal failed: %s %s",
|
|
1279
|
+
symbol,
|
|
1280
|
+
error?.message || String(error)
|
|
1281
|
+
);
|
|
1427
1282
|
}
|
|
1428
|
-
return
|
|
1429
|
-
source: String(breadth.source ?? ""),
|
|
1430
|
-
available: true,
|
|
1431
|
-
universe: String(breadth.universe ?? ""),
|
|
1432
|
-
interval: String(breadth.interval ?? ""),
|
|
1433
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1434
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1435
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1436
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1437
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1438
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1439
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1440
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1441
|
-
};
|
|
1283
|
+
return next;
|
|
1442
1284
|
};
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1285
|
+
|
|
1286
|
+
// src/strategy/portfolioRuntime.ts
|
|
1287
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
1288
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
1289
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
1290
|
+
throw new Error(
|
|
1291
|
+
"Portfolio policies require position marks and entry executions"
|
|
1292
|
+
);
|
|
1293
|
+
const orders = await Promise.all(
|
|
1294
|
+
reservations.map(async (r) => {
|
|
1295
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
1296
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
1297
|
+
symbol: r.symbol,
|
|
1298
|
+
orderId: r.id
|
|
1299
|
+
});
|
|
1300
|
+
if (fact2) return fact2;
|
|
1301
|
+
}
|
|
1302
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
1303
|
+
symbol: r.symbol,
|
|
1304
|
+
startTime: r.timestamp,
|
|
1305
|
+
endTime: asOf
|
|
1306
|
+
}) : [];
|
|
1307
|
+
const filledQty = executions.filter(
|
|
1308
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
1309
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
1450
1310
|
return {
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1311
|
+
id: r.id,
|
|
1312
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
1313
|
+
filledQty,
|
|
1314
|
+
positionId: r.id
|
|
1315
|
+
};
|
|
1316
|
+
})
|
|
1317
|
+
);
|
|
1318
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
1319
|
+
const positions = await Promise.all(
|
|
1320
|
+
snapshots2.map(async (p) => {
|
|
1321
|
+
const reserved = reservations.find(
|
|
1322
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
1323
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
1324
|
+
)
|
|
1325
|
+
);
|
|
1326
|
+
const trade = await getActiveRuntimeTrade({
|
|
1327
|
+
...binding,
|
|
1328
|
+
symbol: p.symbol
|
|
1329
|
+
});
|
|
1330
|
+
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;
|
|
1331
|
+
return {
|
|
1332
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
1333
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
1334
|
+
symbol: p.symbol,
|
|
1335
|
+
direction: p.direction,
|
|
1336
|
+
qty: p.qty,
|
|
1337
|
+
entryPrice: p.price,
|
|
1338
|
+
markPrice: p.currentPrice,
|
|
1339
|
+
markedAt: p.observedAt ?? null,
|
|
1340
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
1463
1341
|
};
|
|
1342
|
+
})
|
|
1343
|
+
);
|
|
1344
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
1345
|
+
}
|
|
1346
|
+
function persistentStore(key) {
|
|
1347
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
1348
|
+
return {
|
|
1349
|
+
async read() {
|
|
1350
|
+
const value = await import_redis2.portfolioStateStorage.read(key);
|
|
1351
|
+
if (value === null) return null;
|
|
1352
|
+
const state = JSON.parse(value);
|
|
1353
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
1354
|
+
raw.set(state, value);
|
|
1355
|
+
return state;
|
|
1356
|
+
},
|
|
1357
|
+
async compare(previous, next) {
|
|
1358
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
1359
|
+
if (expected === void 0)
|
|
1360
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
1361
|
+
return import_redis2.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
1464
1362
|
}
|
|
1465
|
-
return {
|
|
1466
|
-
source: String(breadth.source ?? ""),
|
|
1467
|
-
available: true,
|
|
1468
|
-
universe: String(breadth.universe ?? ""),
|
|
1469
|
-
interval: String(breadth.interval ?? ""),
|
|
1470
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1471
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1472
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1473
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1474
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1475
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1476
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1477
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1478
|
-
};
|
|
1479
1363
|
};
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1364
|
+
}
|
|
1365
|
+
function getPortfolioRuntime({
|
|
1366
|
+
value,
|
|
1367
|
+
connectorName,
|
|
1368
|
+
strategy,
|
|
1369
|
+
connector,
|
|
1370
|
+
env,
|
|
1371
|
+
userName,
|
|
1372
|
+
accountId,
|
|
1373
|
+
deploymentId,
|
|
1374
|
+
symbol,
|
|
1375
|
+
canClose
|
|
1376
|
+
}) {
|
|
1377
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
1378
|
+
if (!policy) return void 0;
|
|
1379
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
1380
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
1381
|
+
throw new Error(
|
|
1382
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
1383
|
+
);
|
|
1384
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
1385
|
+
throw new Error(
|
|
1386
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
1387
|
+
);
|
|
1388
|
+
const binding = {
|
|
1389
|
+
userName,
|
|
1390
|
+
accountId,
|
|
1391
|
+
deploymentId
|
|
1486
1392
|
};
|
|
1393
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
1394
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
1395
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
1396
|
+
const cacheKey = key;
|
|
1397
|
+
let registry = coordinators.get(connector);
|
|
1398
|
+
if (!registry) {
|
|
1399
|
+
registry = /* @__PURE__ */ new Map();
|
|
1400
|
+
coordinators.set(connector, registry);
|
|
1401
|
+
}
|
|
1402
|
+
const existing = registry.get(cacheKey);
|
|
1403
|
+
if (existing) {
|
|
1404
|
+
if (existing.fingerprint !== fingerprint3)
|
|
1405
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
1406
|
+
if (symbol && canClose)
|
|
1407
|
+
existing.permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1408
|
+
return existing.coordinator;
|
|
1409
|
+
}
|
|
1410
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
1411
|
+
if (symbol && canClose)
|
|
1412
|
+
permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1413
|
+
const coordinator = createPortfolioCoordinator({
|
|
1414
|
+
policy,
|
|
1415
|
+
strategy,
|
|
1416
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
1417
|
+
execution: {
|
|
1418
|
+
async confirmClosed(intent, asOf) {
|
|
1419
|
+
if (replay) return;
|
|
1420
|
+
const position = intent.position;
|
|
1421
|
+
await markRuntimeTradeClosed({
|
|
1422
|
+
...binding,
|
|
1423
|
+
strategy: position.strategy,
|
|
1424
|
+
expectedOrderId: position.id,
|
|
1425
|
+
symbol: position.symbol,
|
|
1426
|
+
exitPrice: position.markPrice,
|
|
1427
|
+
exitTimestamp: asOf,
|
|
1428
|
+
exitType: "exit"
|
|
1429
|
+
});
|
|
1430
|
+
},
|
|
1431
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
1432
|
+
async close(position, intent, asOf) {
|
|
1433
|
+
const permission = permissions.get(
|
|
1434
|
+
JSON.stringify([position.strategy, position.symbol])
|
|
1435
|
+
);
|
|
1436
|
+
if (!permission || !await permission(position, intent, asOf)) return;
|
|
1437
|
+
if (!replay) {
|
|
1438
|
+
const trade = await getActiveRuntimeTrade({
|
|
1439
|
+
...binding,
|
|
1440
|
+
symbol: position.symbol
|
|
1441
|
+
});
|
|
1442
|
+
if (!trade || trade.orderId !== position.id)
|
|
1443
|
+
throw new Error("Portfolio close ownership changed");
|
|
1444
|
+
}
|
|
1445
|
+
await connector.closePosition({
|
|
1446
|
+
symbol: position.symbol,
|
|
1447
|
+
direction: position.direction,
|
|
1448
|
+
price: position.markPrice,
|
|
1449
|
+
timestamp: asOf,
|
|
1450
|
+
signal: {
|
|
1451
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
1452
|
+
strategy: position.strategy,
|
|
1453
|
+
symbol: position.symbol,
|
|
1454
|
+
direction: position.direction,
|
|
1455
|
+
interval: "1",
|
|
1456
|
+
timestamp: asOf,
|
|
1457
|
+
prices: {
|
|
1458
|
+
riskRatio: 0,
|
|
1459
|
+
currentPrice: position.markPrice,
|
|
1460
|
+
takeProfitPrice: position.markPrice,
|
|
1461
|
+
stopLossPrice: position.markPrice
|
|
1462
|
+
},
|
|
1463
|
+
figures: {},
|
|
1464
|
+
indicators: {},
|
|
1465
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator, permissions });
|
|
1472
|
+
return coordinator;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
1476
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
1477
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
1478
|
+
|
|
1479
|
+
// src/tradejsConfig.ts
|
|
1480
|
+
var import_fs = __toESM(require("fs"));
|
|
1481
|
+
var import_path = __toESM(require("path"));
|
|
1482
|
+
var import_url = require("url");
|
|
1483
|
+
var import_config = require("@tradejs/core/config");
|
|
1484
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
1485
|
+
var CONFIG_FILE_NAMES = [
|
|
1486
|
+
"tradejs.config.ts",
|
|
1487
|
+
"tradejs.config.mts",
|
|
1488
|
+
"tradejs.config.js",
|
|
1489
|
+
"tradejs.config.mjs",
|
|
1490
|
+
"tradejs.config.cjs"
|
|
1491
|
+
];
|
|
1492
|
+
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1493
|
+
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1494
|
+
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1495
|
+
var tsNodeRegistered = false;
|
|
1496
|
+
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1497
|
+
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1498
|
+
var getTradejsProjectCwd = (cwd) => {
|
|
1499
|
+
const explicit = String(cwd ?? "").trim();
|
|
1500
|
+
if (explicit) {
|
|
1501
|
+
return import_path.default.resolve(explicit);
|
|
1502
|
+
}
|
|
1503
|
+
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1504
|
+
if (fromEnv) {
|
|
1505
|
+
return import_path.default.resolve(fromEnv);
|
|
1506
|
+
}
|
|
1507
|
+
return process.cwd();
|
|
1487
1508
|
};
|
|
1488
|
-
var
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
1492
|
-
if (!targetVsBtc) {
|
|
1493
|
-
return {
|
|
1494
|
-
source: null,
|
|
1495
|
-
available: false,
|
|
1496
|
-
ratioReturn1h: null,
|
|
1497
|
-
ratioReturn4h: null,
|
|
1498
|
-
ratioReturn24h: null,
|
|
1499
|
-
alphaVsBtc1h: null,
|
|
1500
|
-
alphaVsBtc4h: null,
|
|
1501
|
-
alphaVsBtc24h: null,
|
|
1502
|
-
betaToBtc20: null,
|
|
1503
|
-
correlationToBtc20: null,
|
|
1504
|
-
ratioTrend: null
|
|
1505
|
-
};
|
|
1509
|
+
var normalizeConfig = (rawConfig) => {
|
|
1510
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1511
|
+
return {};
|
|
1506
1512
|
}
|
|
1513
|
+
const config = rawConfig;
|
|
1514
|
+
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1515
|
+
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1516
|
+
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1517
|
+
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1518
|
+
config.hooks
|
|
1519
|
+
);
|
|
1520
|
+
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1507
1521
|
return {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
1514
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
1515
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
1516
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
1517
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
1518
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
1522
|
+
strategies: strategies2,
|
|
1523
|
+
indicators,
|
|
1524
|
+
connectors,
|
|
1525
|
+
...hooks ? { hooks } : {},
|
|
1526
|
+
...runtime ? { runtime } : {}
|
|
1519
1527
|
};
|
|
1520
1528
|
};
|
|
1521
|
-
var
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
if (!btcAltRegime) {
|
|
1526
|
-
return {
|
|
1527
|
-
source: null,
|
|
1528
|
-
available: false,
|
|
1529
|
-
universe: null,
|
|
1530
|
-
interval: null,
|
|
1531
|
-
stale: null,
|
|
1532
|
-
regime: null,
|
|
1533
|
-
btcReturn24h: null,
|
|
1534
|
-
altBasketReturn24h: null,
|
|
1535
|
-
btcVsAltReturn24h: null,
|
|
1536
|
-
btcTurnoverShare24h: null,
|
|
1537
|
-
btcTurnoverShareChange24h: null,
|
|
1538
|
-
altVolToBtcVol24h: null,
|
|
1539
|
-
altDispersion24h: null
|
|
1540
|
-
};
|
|
1529
|
+
var getNodeCreateRequire = () => {
|
|
1530
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
1531
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
1532
|
+
return builtinModule.createRequire;
|
|
1541
1533
|
}
|
|
1542
|
-
|
|
1543
|
-
source: String(btcAltRegime.source ?? ""),
|
|
1544
|
-
available: true,
|
|
1545
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
1546
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
1547
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1548
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1549
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1550
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1551
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1552
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1553
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1554
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
1555
|
-
),
|
|
1556
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1557
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1558
|
-
};
|
|
1534
|
+
throw new TypeError("module.createRequire is not available");
|
|
1559
1535
|
};
|
|
1560
|
-
var
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1536
|
+
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
1537
|
+
var ensureTsNodeRegistered = async () => {
|
|
1538
|
+
if (tsNodeRegistered) {
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
const tsNodeModule = await import("ts-node");
|
|
1542
|
+
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1543
|
+
tsNode.register?.({
|
|
1544
|
+
transpileOnly: true,
|
|
1545
|
+
compilerOptions: {
|
|
1546
|
+
module: "Node16",
|
|
1547
|
+
moduleResolution: "node16"
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
tsNodeRegistered = true;
|
|
1551
|
+
};
|
|
1552
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1553
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1554
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1558
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1559
|
+
const register = tsconfigPathsModule.register;
|
|
1560
|
+
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1564
|
+
if (loadedConfig.resultType !== "success") {
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
register({
|
|
1568
|
+
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1569
|
+
paths: loadedConfig.paths,
|
|
1570
|
+
addMatchAll: false
|
|
1571
|
+
});
|
|
1572
|
+
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
1573
|
+
};
|
|
1574
|
+
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1575
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1576
|
+
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
1577
|
+
if (cachedMatcher) {
|
|
1578
|
+
const resolved2 = cachedMatcher(moduleName);
|
|
1579
|
+
return resolved2 || null;
|
|
1580
|
+
}
|
|
1581
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1582
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1583
|
+
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
1584
|
+
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
1585
|
+
return null;
|
|
1586
|
+
}
|
|
1587
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1588
|
+
if (loadedConfig.resultType !== "success") {
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
const matchPath = createMatchPath(
|
|
1592
|
+
loadedConfig.absoluteBaseUrl,
|
|
1593
|
+
loadedConfig.paths
|
|
1594
|
+
);
|
|
1595
|
+
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
1596
|
+
".ts",
|
|
1597
|
+
".tsx",
|
|
1598
|
+
".mts",
|
|
1599
|
+
".cts",
|
|
1600
|
+
".js",
|
|
1601
|
+
".jsx",
|
|
1602
|
+
".mjs",
|
|
1603
|
+
".cjs",
|
|
1604
|
+
".json"
|
|
1605
|
+
]) || "";
|
|
1606
|
+
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1607
|
+
const resolved = matcher(moduleName);
|
|
1608
|
+
return resolved || null;
|
|
1609
|
+
};
|
|
1610
|
+
var toImportSpecifier = (moduleName) => {
|
|
1611
|
+
if (moduleName.startsWith("file://")) {
|
|
1612
|
+
return moduleName;
|
|
1613
|
+
}
|
|
1614
|
+
if (import_path.default.isAbsolute(moduleName)) {
|
|
1615
|
+
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
1616
|
+
}
|
|
1617
|
+
return moduleName;
|
|
1618
|
+
};
|
|
1619
|
+
var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
|
|
1620
|
+
var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
|
|
1621
|
+
var isBareModuleSpecifier = (moduleName) => {
|
|
1622
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1623
|
+
if (!normalized) {
|
|
1624
|
+
return false;
|
|
1625
|
+
}
|
|
1626
|
+
if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
|
|
1627
|
+
return false;
|
|
1628
|
+
}
|
|
1629
|
+
return true;
|
|
1630
|
+
};
|
|
1631
|
+
var importConfigFile = async (configFilePath) => {
|
|
1632
|
+
const ext = import_path.default.extname(configFilePath).toLowerCase();
|
|
1633
|
+
const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
|
|
1634
|
+
if (ext === ".ts" || ext === ".mts") {
|
|
1635
|
+
const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
|
|
1636
|
+
await ensureTsNodeRegistered();
|
|
1637
|
+
await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
|
|
1638
|
+
return requireFn(configFilePath);
|
|
1639
|
+
}
|
|
1640
|
+
return import(
|
|
1641
|
+
/* webpackIgnore: true */
|
|
1642
|
+
configFileUrl
|
|
1643
|
+
);
|
|
1644
|
+
};
|
|
1645
|
+
var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1646
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1647
|
+
if (!normalized) {
|
|
1648
|
+
return {};
|
|
1649
|
+
}
|
|
1650
|
+
let modulePath = normalized;
|
|
1651
|
+
if (normalized.startsWith("file://")) {
|
|
1652
|
+
try {
|
|
1653
|
+
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
1654
|
+
} catch {
|
|
1655
|
+
modulePath = normalized;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
const requireFn = getRequireFn(
|
|
1659
|
+
import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
|
|
1660
|
+
);
|
|
1661
|
+
if (isTsModulePath(modulePath)) {
|
|
1662
|
+
await ensureTsNodeRegistered();
|
|
1663
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1664
|
+
return requireFn(modulePath);
|
|
1665
|
+
}
|
|
1666
|
+
if (isBareModuleSpecifier(normalized)) {
|
|
1667
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1668
|
+
try {
|
|
1669
|
+
return requireFn(normalized);
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
1672
|
+
normalized,
|
|
1673
|
+
cwd
|
|
1674
|
+
);
|
|
1675
|
+
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
1676
|
+
return requireFn(resolvedByTsconfig);
|
|
1677
|
+
}
|
|
1678
|
+
throw error;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
try {
|
|
1682
|
+
return await import(
|
|
1683
|
+
/* webpackIgnore: true */
|
|
1684
|
+
toImportSpecifier(normalized)
|
|
1685
|
+
);
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
if (isTsModulePath(modulePath)) {
|
|
1688
|
+
await ensureTsNodeRegistered();
|
|
1689
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1690
|
+
return requireFn(modulePath);
|
|
1691
|
+
}
|
|
1692
|
+
throw error;
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
var resolveExportedConfig = (moduleExports) => {
|
|
1696
|
+
const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
|
|
1697
|
+
return normalizeConfig(candidate);
|
|
1698
|
+
};
|
|
1699
|
+
var findConfigFilePath = (cwd) => {
|
|
1700
|
+
let currentDir = import_path.default.resolve(cwd);
|
|
1701
|
+
while (true) {
|
|
1702
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
1703
|
+
const fullPath = import_path.default.join(currentDir, fileName);
|
|
1704
|
+
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
1705
|
+
return fullPath;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
const parentDir = import_path.default.dirname(currentDir);
|
|
1709
|
+
if (parentDir === currentDir) {
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
currentDir = parentDir;
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1716
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1717
|
+
if (!normalized) {
|
|
1718
|
+
return "";
|
|
1719
|
+
}
|
|
1720
|
+
if (normalized.startsWith("file://")) {
|
|
1721
|
+
try {
|
|
1722
|
+
return (0, import_url.fileURLToPath)(normalized);
|
|
1723
|
+
} catch {
|
|
1724
|
+
return normalized;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (import_path.default.isAbsolute(normalized)) {
|
|
1728
|
+
return normalized;
|
|
1729
|
+
}
|
|
1730
|
+
if (isRelativeModulePath(normalized)) {
|
|
1731
|
+
return import_path.default.resolve(cwd, normalized);
|
|
1732
|
+
}
|
|
1733
|
+
return normalized;
|
|
1734
|
+
};
|
|
1735
|
+
var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
1736
|
+
const cached = cachedByCwd.get(cwd);
|
|
1737
|
+
if (cached) {
|
|
1738
|
+
return cached;
|
|
1739
|
+
}
|
|
1740
|
+
const configFilePath = findConfigFilePath(cwd);
|
|
1741
|
+
if (!configFilePath) {
|
|
1742
|
+
cachedByCwd.set(cwd, {});
|
|
1743
|
+
return {};
|
|
1744
|
+
}
|
|
1745
|
+
try {
|
|
1746
|
+
const moduleExports = await importConfigFile(configFilePath);
|
|
1747
|
+
const config = resolveExportedConfig(moduleExports);
|
|
1748
|
+
cachedByCwd.set(cwd, config);
|
|
1749
|
+
if (!announcedConfigFile.has(configFilePath)) {
|
|
1750
|
+
announcedConfigFile.add(configFilePath);
|
|
1751
|
+
import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
1752
|
+
}
|
|
1753
|
+
return config;
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
import_logger2.logger.log(
|
|
1756
|
+
"warn",
|
|
1757
|
+
"Failed to load TradeJS config from %s: %s",
|
|
1758
|
+
configFilePath,
|
|
1759
|
+
String(error)
|
|
1760
|
+
);
|
|
1761
|
+
cachedByCwd.set(cwd, {});
|
|
1762
|
+
return {};
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
// src/strategy/manifests.ts
|
|
1767
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
1768
|
+
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
1769
|
+
var sharedRegistryScope = globalThis;
|
|
1770
|
+
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
1771
|
+
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
1772
|
+
});
|
|
1773
|
+
var createStrategyRegistryState = () => ({
|
|
1774
|
+
strategyCreators: /* @__PURE__ */ new Map(),
|
|
1775
|
+
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
1776
|
+
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
1777
|
+
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
1778
|
+
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
1779
|
+
pluginsLoadPromise: null
|
|
1780
|
+
});
|
|
1781
|
+
var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
1782
|
+
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
1783
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1784
|
+
let state = registryStateByProjectRoot.get(projectRoot);
|
|
1785
|
+
if (!state) {
|
|
1786
|
+
state = createStrategyRegistryState();
|
|
1787
|
+
registryStateByProjectRoot.set(projectRoot, state);
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
projectRoot,
|
|
1791
|
+
state
|
|
1792
|
+
};
|
|
1793
|
+
};
|
|
1794
|
+
var toUniqueModules = (modules = []) => [
|
|
1795
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
1796
|
+
];
|
|
1797
|
+
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
1798
|
+
const config = await loadTradejsConfig(cwd);
|
|
1799
|
+
return {
|
|
1800
|
+
strategyModules: toUniqueModules(config.strategies),
|
|
1801
|
+
indicatorModules: toUniqueModules(config.indicators)
|
|
1802
|
+
};
|
|
1803
|
+
};
|
|
1804
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
1805
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
1806
|
+
return null;
|
|
1807
|
+
}
|
|
1808
|
+
const candidate = moduleExport;
|
|
1809
|
+
if (Array.isArray(candidate[key])) {
|
|
1810
|
+
return candidate[key];
|
|
1811
|
+
}
|
|
1812
|
+
const defaultExport = candidate.default;
|
|
1813
|
+
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
1814
|
+
return defaultExport[key];
|
|
1815
|
+
}
|
|
1816
|
+
return null;
|
|
1817
|
+
};
|
|
1818
|
+
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
1819
|
+
const strategyEntries = extractModuleEntries(
|
|
1820
|
+
moduleExport,
|
|
1821
|
+
"strategyEntries"
|
|
1822
|
+
);
|
|
1823
|
+
return strategyEntries ? { strategyEntries } : null;
|
|
1824
|
+
};
|
|
1825
|
+
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
1826
|
+
const indicatorEntries = extractModuleEntries(
|
|
1827
|
+
moduleExport,
|
|
1828
|
+
"indicatorEntries"
|
|
1829
|
+
);
|
|
1830
|
+
return indicatorEntries ? { indicatorEntries } : null;
|
|
1831
|
+
};
|
|
1832
|
+
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
1833
|
+
const issues = [];
|
|
1834
|
+
const names = /* @__PURE__ */ new Set();
|
|
1835
|
+
entries.forEach((entry, index) => {
|
|
1836
|
+
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
1837
|
+
const strategyName = entry?.manifest?.name;
|
|
1838
|
+
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
1839
|
+
issues.push(`${entryPath}: manifest.name is required`);
|
|
1840
|
+
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
1841
|
+
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
1842
|
+
} else {
|
|
1843
|
+
names.add(strategyName);
|
|
1844
|
+
}
|
|
1845
|
+
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
1846
|
+
issues.push(`${entryPath}: defaults must be an object`);
|
|
1847
|
+
}
|
|
1848
|
+
if (typeof entry?.parseConfig !== "function") {
|
|
1849
|
+
issues.push(`${entryPath}: parseConfig is required`);
|
|
1850
|
+
}
|
|
1851
|
+
if (typeof entry?.createCore !== "function") {
|
|
1852
|
+
issues.push(`${entryPath}: createCore is required`);
|
|
1853
|
+
}
|
|
1854
|
+
});
|
|
1855
|
+
return issues;
|
|
1856
|
+
};
|
|
1857
|
+
var registerEntries = (entries, source, state) => {
|
|
1858
|
+
for (const entry of entries) {
|
|
1859
|
+
const strategyName = entry.manifest.name;
|
|
1860
|
+
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
1861
|
+
state.strategyEntriesMap.set(strategyName, entry);
|
|
1862
|
+
state.strategySourcesMap.set(strategyName, source);
|
|
1863
|
+
materializeStrategyCreator(strategyName, state);
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
var materializeStrategyCreator = (strategyName, state) => {
|
|
1867
|
+
if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
const entry = state.strategyEntriesMap.get(strategyName);
|
|
1871
|
+
if (!entry) return;
|
|
1872
|
+
state.strategyCreators.set(
|
|
1873
|
+
strategyName,
|
|
1874
|
+
sharedStrategyRegistry.strategyRuntimeFactory({
|
|
1875
|
+
strategyName,
|
|
1876
|
+
defaults: entry.defaults,
|
|
1877
|
+
createCore: entry.createCore,
|
|
1878
|
+
manifest: entry.manifest,
|
|
1879
|
+
detectorKey: entry.detectorKey,
|
|
1880
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1881
|
+
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
1882
|
+
})
|
|
1883
|
+
);
|
|
1884
|
+
};
|
|
1885
|
+
var setStrategyRuntimeFactory = (factory) => {
|
|
1886
|
+
sharedStrategyRegistry.strategyRuntimeFactory = factory;
|
|
1887
|
+
for (const state of registryStateByProjectRoot.values()) {
|
|
1888
|
+
for (const strategyName of state.strategyEntriesMap.keys()) {
|
|
1889
|
+
materializeStrategyCreator(strategyName, state);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1894
|
+
if (typeof importTradejsModule === "function") {
|
|
1895
|
+
return importTradejsModule(moduleName, cwd);
|
|
1896
|
+
}
|
|
1897
|
+
return import(
|
|
1898
|
+
/* webpackIgnore: true */
|
|
1899
|
+
moduleName
|
|
1900
|
+
);
|
|
1901
|
+
};
|
|
1902
|
+
var resolveStrategyModuleEntry = async ({
|
|
1903
|
+
strategyName,
|
|
1904
|
+
moduleName,
|
|
1905
|
+
cwd
|
|
1906
|
+
}) => {
|
|
1907
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1908
|
+
const source = moduleName.trim();
|
|
1909
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
1910
|
+
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
1911
|
+
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
1912
|
+
if (cached) return cached;
|
|
1913
|
+
const resolution = (async () => {
|
|
1914
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1915
|
+
resolvedModuleName,
|
|
1916
|
+
projectRoot
|
|
1917
|
+
);
|
|
1918
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1919
|
+
if (!pluginDefinition) {
|
|
1920
|
+
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
1921
|
+
}
|
|
1922
|
+
const issues = validateStrategyEntries(
|
|
1923
|
+
source,
|
|
1924
|
+
pluginDefinition.strategyEntries
|
|
1925
|
+
);
|
|
1926
|
+
if (issues.length > 0) {
|
|
1927
|
+
throw new Error(
|
|
1928
|
+
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
const entry = pluginDefinition.strategyEntries.find(
|
|
1932
|
+
(candidate) => candidate.manifest.name === strategyName
|
|
1933
|
+
);
|
|
1934
|
+
if (!entry) {
|
|
1935
|
+
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
1936
|
+
}
|
|
1937
|
+
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
1938
|
+
if (!factory) {
|
|
1939
|
+
throw new Error("Strategy runtime factory is not configured");
|
|
1940
|
+
}
|
|
1941
|
+
const manifestsByName = new Map(
|
|
1942
|
+
pluginDefinition.strategyEntries.map(
|
|
1943
|
+
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
1944
|
+
)
|
|
1945
|
+
);
|
|
1946
|
+
return {
|
|
1947
|
+
entry,
|
|
1948
|
+
creator: factory({
|
|
1949
|
+
strategyName,
|
|
1950
|
+
defaults: entry.defaults,
|
|
1951
|
+
createCore: entry.createCore,
|
|
1952
|
+
manifest: entry.manifest,
|
|
1953
|
+
detectorKey: entry.detectorKey,
|
|
1954
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1955
|
+
resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
|
|
1956
|
+
}),
|
|
1957
|
+
source
|
|
1587
1958
|
};
|
|
1959
|
+
})();
|
|
1960
|
+
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
1961
|
+
try {
|
|
1962
|
+
return await resolution;
|
|
1963
|
+
} catch (error) {
|
|
1964
|
+
state.strategyModuleResolutions.delete(cacheKey);
|
|
1965
|
+
throw error;
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
1969
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1970
|
+
if (!state.pluginsLoadPromise) {
|
|
1971
|
+
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
1972
|
+
state.pluginsLoadPromise = (async () => {
|
|
1973
|
+
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
1974
|
+
const strategySet = new Set(strategyModules);
|
|
1975
|
+
const indicatorSet = new Set(indicatorModules);
|
|
1976
|
+
const pluginModuleNames = [
|
|
1977
|
+
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
1978
|
+
];
|
|
1979
|
+
if (!pluginModuleNames.length) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const issues = [];
|
|
1983
|
+
for (const moduleName of pluginModuleNames) {
|
|
1984
|
+
try {
|
|
1985
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
1986
|
+
moduleName,
|
|
1987
|
+
projectRoot
|
|
1988
|
+
);
|
|
1989
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1990
|
+
resolvedModuleName,
|
|
1991
|
+
projectRoot
|
|
1992
|
+
);
|
|
1993
|
+
if (strategySet.has(moduleName)) {
|
|
1994
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1995
|
+
if (!pluginDefinition) {
|
|
1996
|
+
issues.push(
|
|
1997
|
+
`${moduleName}: export { strategyEntries } is missing`
|
|
1998
|
+
);
|
|
1999
|
+
} else {
|
|
2000
|
+
const entryIssues = validateStrategyEntries(
|
|
2001
|
+
moduleName,
|
|
2002
|
+
pluginDefinition.strategyEntries,
|
|
2003
|
+
state
|
|
2004
|
+
);
|
|
2005
|
+
issues.push(...entryIssues);
|
|
2006
|
+
if (entryIssues.length === 0) {
|
|
2007
|
+
registerEntries(
|
|
2008
|
+
pluginDefinition.strategyEntries,
|
|
2009
|
+
moduleName,
|
|
2010
|
+
state
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
if (indicatorSet.has(moduleName)) {
|
|
2016
|
+
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
2017
|
+
if (!indicatorPluginDefinition) {
|
|
2018
|
+
issues.push(
|
|
2019
|
+
`${moduleName}: export { indicatorEntries } is missing`
|
|
2020
|
+
);
|
|
2021
|
+
} else {
|
|
2022
|
+
(0, import_indicators.registerIndicatorEntries)(
|
|
2023
|
+
indicatorPluginDefinition.indicatorEntries,
|
|
2024
|
+
moduleName,
|
|
2025
|
+
projectRoot
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
2030
|
+
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2031
|
+
}
|
|
2032
|
+
} catch (error) {
|
|
2033
|
+
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
if (issues.length > 0) {
|
|
2037
|
+
throw new Error(
|
|
2038
|
+
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
})();
|
|
1588
2042
|
}
|
|
1589
|
-
|
|
1590
|
-
source: String(cmcGlobal.source ?? ""),
|
|
1591
|
-
available: true,
|
|
1592
|
-
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
1593
|
-
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
1594
|
-
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
1595
|
-
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
1596
|
-
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
1597
|
-
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
1598
|
-
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
1599
|
-
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
1600
|
-
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
1601
|
-
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
1602
|
-
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
1603
|
-
btcDominanceChange24hPct: toFiniteNumber(
|
|
1604
|
-
cmcGlobal.btcDominanceChange24hPct
|
|
1605
|
-
),
|
|
1606
|
-
ethDominanceChange24hPct: toFiniteNumber(
|
|
1607
|
-
cmcGlobal.ethDominanceChange24hPct
|
|
1608
|
-
),
|
|
1609
|
-
altMarketCapChange24hPct: toFiniteNumber(
|
|
1610
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
1611
|
-
),
|
|
1612
|
-
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1613
|
-
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1614
|
-
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1615
|
-
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1616
|
-
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1617
|
-
};
|
|
2043
|
+
await state.pluginsLoadPromise;
|
|
1618
2044
|
};
|
|
1619
|
-
var
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
if (!cmcReferenceAssets) {
|
|
1624
|
-
return {
|
|
1625
|
-
source: null,
|
|
1626
|
-
available: false,
|
|
1627
|
-
interval: null,
|
|
1628
|
-
asOfTs: null,
|
|
1629
|
-
stale: null,
|
|
1630
|
-
btcMarketCapUsd: null,
|
|
1631
|
-
ethMarketCapUsd: null,
|
|
1632
|
-
btcVolumeUsd: null,
|
|
1633
|
-
ethVolumeUsd: null,
|
|
1634
|
-
btcVolumeToMarketCap: null,
|
|
1635
|
-
ethVolumeToMarketCap: null,
|
|
1636
|
-
ethBtcMarketCapRatio: null,
|
|
1637
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
1638
|
-
ethVsBtcVolumeRatio: null,
|
|
1639
|
-
referenceLiquidityRegime: null
|
|
1640
|
-
};
|
|
2045
|
+
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2046
|
+
if (moduleName?.trim()) {
|
|
2047
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2048
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
|
|
1641
2049
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
1646
|
-
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
1647
|
-
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
1648
|
-
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
1649
|
-
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
1650
|
-
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
1651
|
-
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
1652
|
-
btcVolumeToMarketCap: toFiniteNumber(
|
|
1653
|
-
cmcReferenceAssets.btcVolumeToMarketCap
|
|
1654
|
-
),
|
|
1655
|
-
ethVolumeToMarketCap: toFiniteNumber(
|
|
1656
|
-
cmcReferenceAssets.ethVolumeToMarketCap
|
|
1657
|
-
),
|
|
1658
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1659
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1660
|
-
),
|
|
1661
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1662
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1663
|
-
),
|
|
1664
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1665
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1666
|
-
};
|
|
2050
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2051
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2052
|
+
return state.strategyCreators.get(name);
|
|
1667
2053
|
};
|
|
1668
|
-
var
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
1672
|
-
if (!cmcExchangeLiquidity) {
|
|
1673
|
-
return {
|
|
1674
|
-
source: null,
|
|
1675
|
-
available: false,
|
|
1676
|
-
interval: null,
|
|
1677
|
-
asOfTs: null,
|
|
1678
|
-
stale: null,
|
|
1679
|
-
exchangesCount: null,
|
|
1680
|
-
totalVolumeUsd: null,
|
|
1681
|
-
totalVolumeChange24hPct: null,
|
|
1682
|
-
binanceVolumeUsd: null,
|
|
1683
|
-
binanceVolumeShare: null,
|
|
1684
|
-
topExchangeVolumeShare: null,
|
|
1685
|
-
liquidityRegime: null
|
|
1686
|
-
};
|
|
2054
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
2055
|
+
if (!name) {
|
|
2056
|
+
return void 0;
|
|
1687
2057
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
available: true,
|
|
1691
|
-
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
1692
|
-
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
1693
|
-
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
1694
|
-
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
1695
|
-
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
1696
|
-
totalVolumeChange24hPct: toFiniteNumber(
|
|
1697
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
1698
|
-
),
|
|
1699
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
1700
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
1701
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
1702
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
1703
|
-
),
|
|
1704
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
1705
|
-
};
|
|
2058
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2059
|
+
return state.strategyManifestsMap.get(name);
|
|
1706
2060
|
};
|
|
1707
|
-
var
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
};
|
|
2061
|
+
var strategies = new Proxy(
|
|
2062
|
+
{},
|
|
2063
|
+
{
|
|
2064
|
+
get: (_target, property) => {
|
|
2065
|
+
if (typeof property !== "string") {
|
|
2066
|
+
return void 0;
|
|
2067
|
+
}
|
|
2068
|
+
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
2069
|
+
},
|
|
2070
|
+
ownKeys: () => {
|
|
2071
|
+
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
2072
|
+
},
|
|
2073
|
+
getOwnPropertyDescriptor: () => ({
|
|
2074
|
+
enumerable: true,
|
|
2075
|
+
configurable: true
|
|
2076
|
+
})
|
|
1724
2077
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2078
|
+
);
|
|
2079
|
+
|
|
2080
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
2081
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
2082
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
2083
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
2084
|
+
if (declared) return declared;
|
|
2085
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
2086
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
2087
|
+
if (!provider && !universeId) return void 0;
|
|
2088
|
+
if (!provider || !universeId)
|
|
2089
|
+
throw new Error(
|
|
2090
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
2091
|
+
);
|
|
2092
|
+
return { provider, universeId };
|
|
1737
2093
|
};
|
|
1738
|
-
var
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
2094
|
+
var loadTickerProfileContext = async ({
|
|
2095
|
+
projectRoot,
|
|
2096
|
+
provider,
|
|
2097
|
+
universeId,
|
|
2098
|
+
symbol,
|
|
2099
|
+
timestamp
|
|
2100
|
+
}) => {
|
|
2101
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
2102
|
+
let cached = snapshots.get(key);
|
|
2103
|
+
if (!cached || cached.expires < Date.now()) {
|
|
2104
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
2105
|
+
if (!envelope) return null;
|
|
2106
|
+
const s = envelope.snapshot;
|
|
2107
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
2108
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
2109
|
+
const entry = snapshots.get(key);
|
|
2110
|
+
if (entry) entry.expires = Infinity;
|
|
2111
|
+
return new Map(
|
|
2112
|
+
s.profiles.map((p) => [
|
|
2113
|
+
p.symbol,
|
|
2114
|
+
Object.freeze({
|
|
2115
|
+
...p,
|
|
2116
|
+
definition: s.definition,
|
|
2117
|
+
provider,
|
|
2118
|
+
universeId,
|
|
2119
|
+
snapshotId: envelope.snapshotId
|
|
2120
|
+
})
|
|
2121
|
+
])
|
|
2122
|
+
);
|
|
2123
|
+
}).catch((error) => {
|
|
2124
|
+
snapshots.delete(key);
|
|
2125
|
+
throw error;
|
|
2126
|
+
});
|
|
2127
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
2128
|
+
snapshots.set(key, cached);
|
|
2129
|
+
if (snapshots.size > 24) {
|
|
2130
|
+
const oldest = snapshots.keys().next().value;
|
|
2131
|
+
if (oldest) snapshots.delete(oldest);
|
|
2132
|
+
}
|
|
1761
2133
|
}
|
|
1762
|
-
return
|
|
1763
|
-
source: String(cmcIndexes.source ?? ""),
|
|
1764
|
-
available: true,
|
|
1765
|
-
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
1766
|
-
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
1767
|
-
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
1768
|
-
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
1769
|
-
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
1770
|
-
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
1771
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
1772
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
1773
|
-
),
|
|
1774
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
1775
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1776
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1777
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1778
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1779
|
-
),
|
|
1780
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1781
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1782
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1783
|
-
),
|
|
1784
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1785
|
-
};
|
|
2134
|
+
return (await cached.value)?.get(symbol);
|
|
1786
2135
|
};
|
|
1787
|
-
var
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
2136
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
2137
|
+
Object.create(
|
|
2138
|
+
Object.getPrototypeOf(baseContext),
|
|
2139
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
2140
|
+
),
|
|
2141
|
+
"tickerProfile",
|
|
2142
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
2143
|
+
);
|
|
2144
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
2145
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
2146
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
2147
|
+
return;
|
|
2148
|
+
const profile = await loadTickerProfileContext({
|
|
2149
|
+
...source,
|
|
2150
|
+
projectRoot: getTradejsProjectCwd(),
|
|
2151
|
+
symbol: signal.symbol,
|
|
2152
|
+
timestamp: signal.timestamp
|
|
2153
|
+
});
|
|
2154
|
+
if (profile) {
|
|
2155
|
+
signal.additionalIndicators = {
|
|
2156
|
+
...signal.additionalIndicators,
|
|
2157
|
+
tickerProfile: profile
|
|
1802
2158
|
};
|
|
1803
2159
|
}
|
|
1804
|
-
return {
|
|
1805
|
-
source: String(refs.source ?? ""),
|
|
1806
|
-
available: true,
|
|
1807
|
-
primaryReferenceSymbol,
|
|
1808
|
-
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
1809
|
-
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
1810
|
-
primaryTradeFlow?.buyPressurePct
|
|
1811
|
-
),
|
|
1812
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1813
|
-
};
|
|
1814
2160
|
};
|
|
1815
|
-
var buildAiMarketContext = (signal) => ({
|
|
1816
|
-
execution: {
|
|
1817
|
-
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
1818
|
-
},
|
|
1819
|
-
participation: {
|
|
1820
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
1821
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
1822
|
-
},
|
|
1823
|
-
relative: {
|
|
1824
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
1825
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
1826
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
1827
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
1828
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
1829
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
1830
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
1831
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1832
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1833
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
1834
|
-
}
|
|
1835
|
-
});
|
|
1836
2161
|
|
|
1837
|
-
// src/
|
|
1838
|
-
var
|
|
2162
|
+
// src/strategyRuntime.ts
|
|
2163
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
1839
2164
|
|
|
1840
|
-
// src/
|
|
1841
|
-
var
|
|
1842
|
-
var
|
|
1843
|
-
var
|
|
1844
|
-
var
|
|
1845
|
-
var
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
]
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2165
|
+
// src/jev.ts
|
|
2166
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
2167
|
+
var import_node_path = __toESM(require("path"));
|
|
2168
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
2169
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
2170
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
2171
|
+
init_jevInput();
|
|
2172
|
+
init_jevInput();
|
|
2173
|
+
init_jevTraining();
|
|
2174
|
+
var resolveJevProvider = async (userName) => {
|
|
2175
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2176
|
+
const provider = {
|
|
2177
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
2178
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
2179
|
+
};
|
|
2180
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
2181
|
+
if (!settings.JEV_API_KEY)
|
|
2182
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2183
|
+
return provider;
|
|
2184
|
+
};
|
|
2185
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
2186
|
+
var pending = /* @__PURE__ */ new Map();
|
|
2187
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
2188
|
+
var evaluateJevInput = async ({
|
|
2189
|
+
input,
|
|
2190
|
+
config,
|
|
2191
|
+
userName,
|
|
2192
|
+
projectRoot
|
|
2193
|
+
}) => {
|
|
2194
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
2195
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
2196
|
+
const questions = questionsForJevInput(input);
|
|
2197
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
2198
|
+
const base = {
|
|
2199
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2200
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2201
|
+
source: config.source,
|
|
2202
|
+
mode: config.mode,
|
|
2203
|
+
inputHash,
|
|
2204
|
+
questionsHash,
|
|
2205
|
+
geometryStatus: input.geometryStatus,
|
|
2206
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
2207
|
+
};
|
|
2208
|
+
if (!input.questions.length)
|
|
2209
|
+
return {
|
|
2210
|
+
assessment: {
|
|
2211
|
+
...base,
|
|
2212
|
+
status: "unavailable",
|
|
2213
|
+
model: "",
|
|
2214
|
+
scores: emptyScores(),
|
|
2215
|
+
confidence: emptyScores()
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
if (config.source === "local") {
|
|
2219
|
+
const contents = await import_promises.default.readFile(
|
|
2220
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
2221
|
+
"utf8"
|
|
2222
|
+
);
|
|
2223
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
2224
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
2225
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
2226
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
2227
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
2228
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
2229
|
+
const scores = Object.fromEntries(
|
|
2230
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2231
|
+
key,
|
|
2232
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
2233
|
+
])
|
|
2234
|
+
);
|
|
2235
|
+
return {
|
|
2236
|
+
assessment: {
|
|
2237
|
+
...base,
|
|
2238
|
+
status: "available",
|
|
2239
|
+
model: config.modelSha256,
|
|
2240
|
+
scores,
|
|
2241
|
+
confidence: emptyScores()
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
1863
2244
|
}
|
|
1864
|
-
const
|
|
1865
|
-
|
|
1866
|
-
|
|
2245
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
2246
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
2247
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
2248
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
2249
|
+
const getRecord = async () => {
|
|
2250
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
2251
|
+
if (existing) {
|
|
2252
|
+
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))
|
|
2253
|
+
throw new Error("Jev recording identity mismatch");
|
|
2254
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
2255
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
2256
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
2257
|
+
if (existing.scores[key] !== expected)
|
|
2258
|
+
throw new Error("Jev recording scores do not match the response");
|
|
2259
|
+
}
|
|
2260
|
+
return existing;
|
|
2261
|
+
}
|
|
2262
|
+
if (config.source === "recorded")
|
|
2263
|
+
throw new Error(
|
|
2264
|
+
`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.`
|
|
2265
|
+
);
|
|
2266
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2267
|
+
if (!settings.JEV_API_KEY)
|
|
2268
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2269
|
+
const apiKey = settings.JEV_API_KEY;
|
|
2270
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
2271
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
2272
|
+
throw new Error(
|
|
2273
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
2274
|
+
);
|
|
2275
|
+
const started = Date.now();
|
|
2276
|
+
const state = stateForJevInput(input);
|
|
2277
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
2278
|
+
state,
|
|
2279
|
+
questionsHash,
|
|
2280
|
+
provider,
|
|
2281
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
2282
|
+
});
|
|
2283
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
2284
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
2285
|
+
if (!responsePromise) {
|
|
2286
|
+
responsePromise = (async () => {
|
|
2287
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
2288
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
2289
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
2290
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
2291
|
+
return fresh;
|
|
2292
|
+
})();
|
|
2293
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
2294
|
+
}
|
|
2295
|
+
let response;
|
|
2296
|
+
try {
|
|
2297
|
+
response = await responsePromise;
|
|
2298
|
+
} finally {
|
|
2299
|
+
pendingResponses.delete(responseFile);
|
|
2300
|
+
}
|
|
2301
|
+
const scores = Object.fromEntries(
|
|
2302
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2303
|
+
key,
|
|
2304
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
2305
|
+
])
|
|
2306
|
+
);
|
|
2307
|
+
const value = {
|
|
2308
|
+
schema: "tradejs-jev-record/v4",
|
|
2309
|
+
id,
|
|
2310
|
+
inputHash,
|
|
2311
|
+
questionsHash,
|
|
2312
|
+
provider,
|
|
2313
|
+
input,
|
|
2314
|
+
response,
|
|
2315
|
+
scores,
|
|
2316
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2317
|
+
elapsedMs: Date.now() - started
|
|
2318
|
+
};
|
|
2319
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
2320
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
2321
|
+
};
|
|
2322
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
2323
|
+
let promise = pending.get(pendingKey);
|
|
2324
|
+
if (!promise) {
|
|
2325
|
+
promise = getRecord();
|
|
2326
|
+
pending.set(pendingKey, promise);
|
|
1867
2327
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2328
|
+
let record;
|
|
2329
|
+
try {
|
|
2330
|
+
record = await promise;
|
|
2331
|
+
} finally {
|
|
2332
|
+
pending.delete(pendingKey);
|
|
1873
2333
|
}
|
|
1874
|
-
const config = rawConfig;
|
|
1875
|
-
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1876
|
-
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1877
|
-
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1878
|
-
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1879
|
-
config.hooks
|
|
1880
|
-
);
|
|
1881
|
-
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1882
2334
|
return {
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2335
|
+
record,
|
|
2336
|
+
assessment: {
|
|
2337
|
+
...base,
|
|
2338
|
+
status: "available",
|
|
2339
|
+
recordId: id,
|
|
2340
|
+
model: record.response.model,
|
|
2341
|
+
scores: record.scores,
|
|
2342
|
+
confidence: Object.fromEntries(
|
|
2343
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2344
|
+
key,
|
|
2345
|
+
record.response.answers[key]?.confidence ?? null
|
|
2346
|
+
])
|
|
2347
|
+
)
|
|
2348
|
+
}
|
|
1888
2349
|
};
|
|
1889
2350
|
};
|
|
1890
|
-
var
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2351
|
+
var assessSignalWithJev = async ({
|
|
2352
|
+
signal,
|
|
2353
|
+
config,
|
|
2354
|
+
userName,
|
|
2355
|
+
projectRoot,
|
|
2356
|
+
strict,
|
|
2357
|
+
scope = "runtime"
|
|
2358
|
+
}) => {
|
|
2359
|
+
let assessment;
|
|
2360
|
+
try {
|
|
2361
|
+
const input = buildJevInput(signal);
|
|
2362
|
+
({ assessment } = await evaluateJevInput({
|
|
2363
|
+
input,
|
|
2364
|
+
config,
|
|
2365
|
+
userName,
|
|
2366
|
+
projectRoot
|
|
2367
|
+
}));
|
|
2368
|
+
} catch (error) {
|
|
2369
|
+
if (strict) throw error;
|
|
2370
|
+
assessment = {
|
|
2371
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2372
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2373
|
+
source: config.source,
|
|
2374
|
+
mode: config.mode,
|
|
2375
|
+
status: "unavailable",
|
|
2376
|
+
inputHash: "",
|
|
2377
|
+
questionsHash: "",
|
|
2378
|
+
geometryStatus: "absent",
|
|
2379
|
+
levelsValid: null,
|
|
2380
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
2381
|
+
scores: emptyScores(),
|
|
2382
|
+
confidence: emptyScores()
|
|
2383
|
+
};
|
|
1894
2384
|
}
|
|
1895
|
-
|
|
2385
|
+
signal.assessment = assessment;
|
|
2386
|
+
signal.additionalIndicators = {
|
|
2387
|
+
...signal.additionalIndicators,
|
|
2388
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
2389
|
+
};
|
|
2390
|
+
const identity = {
|
|
2391
|
+
signalId: signal.signalId,
|
|
2392
|
+
strategy: signal.strategy,
|
|
2393
|
+
symbol: signal.symbol,
|
|
2394
|
+
timestamp: signal.timestamp,
|
|
2395
|
+
scope,
|
|
2396
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
2397
|
+
config,
|
|
2398
|
+
assessment
|
|
2399
|
+
};
|
|
2400
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
2401
|
+
import_node_path.default.resolve(
|
|
2402
|
+
projectRoot,
|
|
2403
|
+
config.recordsDir ?? "data/ai/jev",
|
|
2404
|
+
"decisions",
|
|
2405
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2406
|
+
),
|
|
2407
|
+
identity
|
|
2408
|
+
);
|
|
2409
|
+
return assessment;
|
|
1896
2410
|
};
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
2411
|
+
|
|
2412
|
+
// src/strategyRuntime.ts
|
|
2413
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
2414
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
2415
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
2416
|
+
|
|
2417
|
+
// src/strategyHelpers/runtime.ts
|
|
2418
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
2419
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
2420
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
2421
|
+
|
|
2422
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2423
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
2424
|
+
|
|
2425
|
+
// src/strategy/entryExecutionValidation.ts
|
|
2426
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
2427
|
+
constructor() {
|
|
2428
|
+
super(...arguments);
|
|
2429
|
+
this.name = "EntrySubmissionValidationError";
|
|
1901
2430
|
}
|
|
1902
|
-
const tsNodeModule = await import("ts-node");
|
|
1903
|
-
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1904
|
-
tsNode.register?.({
|
|
1905
|
-
transpileOnly: true,
|
|
1906
|
-
compilerOptions: {
|
|
1907
|
-
module: "Node16",
|
|
1908
|
-
moduleResolution: "node16"
|
|
1909
|
-
}
|
|
1910
|
-
});
|
|
1911
|
-
tsNodeRegistered = true;
|
|
1912
2431
|
};
|
|
1913
|
-
var
|
|
1914
|
-
const
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
const
|
|
1919
|
-
const
|
|
1920
|
-
const
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
}
|
|
1928
|
-
register({
|
|
1929
|
-
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1930
|
-
paths: loadedConfig.paths,
|
|
1931
|
-
addMatchAll: false
|
|
1932
|
-
});
|
|
1933
|
-
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
2432
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
2433
|
+
const side = config[direction];
|
|
2434
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
2435
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
2436
|
+
if (minimum == null && maximum == null) return void 0;
|
|
2437
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
2438
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
2439
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
2440
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
2441
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
2442
|
+
const slippageBps = slippage + impact;
|
|
2443
|
+
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)
|
|
2444
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
2445
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
1934
2446
|
};
|
|
1935
|
-
var
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
const
|
|
1953
|
-
|
|
1954
|
-
|
|
2447
|
+
var validateEntryExecution = ({
|
|
2448
|
+
direction,
|
|
2449
|
+
price,
|
|
2450
|
+
qty,
|
|
2451
|
+
stopLossPrice,
|
|
2452
|
+
takeProfitPrice,
|
|
2453
|
+
limits
|
|
2454
|
+
}) => {
|
|
2455
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
2456
|
+
throw new EntrySubmissionValidationError(
|
|
2457
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2458
|
+
);
|
|
2459
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
2460
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
2461
|
+
throw new EntrySubmissionValidationError(
|
|
2462
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
2463
|
+
);
|
|
2464
|
+
const slippage = limits.slippageBps / 1e4;
|
|
2465
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
2466
|
+
const entryCost = cost(price, side);
|
|
2467
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
2468
|
+
const rewardPerUnit = Math.max(
|
|
2469
|
+
0,
|
|
2470
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
1955
2471
|
);
|
|
1956
|
-
const
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
};
|
|
1971
|
-
var toImportSpecifier = (moduleName) => {
|
|
1972
|
-
if (moduleName.startsWith("file://")) {
|
|
1973
|
-
return moduleName;
|
|
1974
|
-
}
|
|
1975
|
-
if (import_path.default.isAbsolute(moduleName)) {
|
|
1976
|
-
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
1977
|
-
}
|
|
1978
|
-
return moduleName;
|
|
2472
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
2473
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
2474
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
2475
|
+
throw new EntrySubmissionValidationError(
|
|
2476
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2477
|
+
);
|
|
2478
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
2479
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
2480
|
+
return {
|
|
2481
|
+
netRiskRatio,
|
|
2482
|
+
plannedLossValue,
|
|
2483
|
+
riskExceeded,
|
|
2484
|
+
belowMinimumNetRiskRatio
|
|
2485
|
+
};
|
|
1979
2486
|
};
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
var
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2487
|
+
|
|
2488
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2489
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
2490
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2491
|
+
var getOrderArrivalSnapshot = async ({
|
|
2492
|
+
connector,
|
|
2493
|
+
symbol,
|
|
2494
|
+
requireTimestamp = false
|
|
2495
|
+
}) => {
|
|
2496
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2497
|
+
return {
|
|
2498
|
+
arrivalSnapshotTime: Date.now(),
|
|
2499
|
+
arrivalSource: "unavailable",
|
|
2500
|
+
bid: null,
|
|
2501
|
+
ask: null,
|
|
2502
|
+
arrivalMid: null,
|
|
2503
|
+
spreadBps: null
|
|
2504
|
+
};
|
|
1989
2505
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
const
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2506
|
+
try {
|
|
2507
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
2508
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
2509
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
2510
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
2511
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
2512
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
2513
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
2514
|
+
return {
|
|
2515
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
2516
|
+
arrivalSource: "top_of_book",
|
|
2517
|
+
bid,
|
|
2518
|
+
ask,
|
|
2519
|
+
arrivalMid,
|
|
2520
|
+
spreadBps
|
|
2521
|
+
};
|
|
2522
|
+
} catch (error) {
|
|
2523
|
+
import_logger3.logger.warn(
|
|
2524
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
2525
|
+
symbol,
|
|
2526
|
+
error?.message || String(error)
|
|
2527
|
+
);
|
|
2528
|
+
return {
|
|
2529
|
+
arrivalSnapshotTime: Date.now(),
|
|
2530
|
+
arrivalSource: "top_of_book_error",
|
|
2531
|
+
bid: null,
|
|
2532
|
+
ask: null,
|
|
2533
|
+
arrivalMid: null,
|
|
2534
|
+
spreadBps: null
|
|
2535
|
+
};
|
|
2000
2536
|
}
|
|
2001
|
-
return import(
|
|
2002
|
-
/* webpackIgnore: true */
|
|
2003
|
-
configFileUrl
|
|
2004
|
-
);
|
|
2005
2537
|
};
|
|
2006
|
-
var
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2538
|
+
var validateEntryProtectionAtArrival = ({
|
|
2539
|
+
direction,
|
|
2540
|
+
signalPrice,
|
|
2541
|
+
bid,
|
|
2542
|
+
ask,
|
|
2543
|
+
arrivalMid,
|
|
2544
|
+
takeProfits,
|
|
2545
|
+
stopLossPrice
|
|
2546
|
+
}) => {
|
|
2547
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
2548
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
2549
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
2550
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
2010
2551
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
2015
|
-
} catch {
|
|
2016
|
-
modulePath = normalized;
|
|
2552
|
+
for (const takeProfit of takeProfits) {
|
|
2553
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
2554
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
2017
2555
|
}
|
|
2018
2556
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
);
|
|
2022
|
-
if (isTsModulePath(modulePath)) {
|
|
2023
|
-
await ensureTsNodeRegistered();
|
|
2024
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2025
|
-
return requireFn(modulePath);
|
|
2557
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
2558
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
2026
2559
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2560
|
+
};
|
|
2561
|
+
var createEntrySubmissionValidation = ({
|
|
2562
|
+
connector,
|
|
2563
|
+
symbol,
|
|
2564
|
+
direction,
|
|
2565
|
+
takeProfits,
|
|
2566
|
+
stopLossPrice,
|
|
2567
|
+
limits,
|
|
2568
|
+
onValidated
|
|
2569
|
+
}) => {
|
|
2570
|
+
if (!limits) return void 0;
|
|
2571
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
2572
|
+
throw new EntrySubmissionValidationError(
|
|
2573
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
2574
|
+
);
|
|
2575
|
+
return async ({ qty, executionPrice }) => {
|
|
2576
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
2577
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
2578
|
+
arrivalSnapshotTime: Date.now(),
|
|
2579
|
+
arrivalSource: "simulation",
|
|
2580
|
+
bid: executionPrice,
|
|
2581
|
+
ask: executionPrice,
|
|
2582
|
+
arrivalMid: executionPrice,
|
|
2583
|
+
spreadBps: 0
|
|
2584
|
+
} : await getOrderArrivalSnapshot({
|
|
2585
|
+
connector,
|
|
2586
|
+
symbol,
|
|
2587
|
+
requireTimestamp: true
|
|
2588
|
+
});
|
|
2589
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
2590
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
2591
|
+
throw new EntrySubmissionValidationError(
|
|
2592
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
2035
2593
|
);
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2594
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
2595
|
+
throw new EntrySubmissionValidationError(
|
|
2596
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
2597
|
+
);
|
|
2598
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
2599
|
+
validateEntryProtectionAtArrival({
|
|
2600
|
+
direction,
|
|
2601
|
+
signalPrice: price,
|
|
2602
|
+
bid: snapshot2.bid,
|
|
2603
|
+
ask: snapshot2.ask,
|
|
2604
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
2605
|
+
takeProfits,
|
|
2606
|
+
stopLossPrice
|
|
2607
|
+
});
|
|
2608
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
2609
|
+
const economics2 = validateEntryExecution({
|
|
2610
|
+
direction,
|
|
2611
|
+
price,
|
|
2612
|
+
qty,
|
|
2613
|
+
stopLossPrice,
|
|
2614
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
2615
|
+
limits
|
|
2616
|
+
});
|
|
2617
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
2618
|
+
import_logger3.logger.warn(
|
|
2619
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
2620
|
+
symbol,
|
|
2621
|
+
direction,
|
|
2622
|
+
economics2.netRiskRatio,
|
|
2623
|
+
limits.minimumNetRiskRatio
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
onValidated?.(snapshot2, economics2);
|
|
2627
|
+
};
|
|
2628
|
+
};
|
|
2629
|
+
|
|
2630
|
+
// src/ai.ts
|
|
2631
|
+
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
2632
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
2633
|
+
|
|
2634
|
+
// src/strategyAdapters/ai.ts
|
|
2635
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
2636
|
+
var import_aiFeatures = require("@tradejs/core/aiFeatures");
|
|
2637
|
+
|
|
2638
|
+
// src/aiShared.ts
|
|
2639
|
+
var MAX_AI_SERIES_POINTS = 5;
|
|
2640
|
+
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
2641
|
+
"tradejs.indicators.compactSnapshot"
|
|
2642
|
+
);
|
|
2643
|
+
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
2644
|
+
var trimSeriesDeep = (value) => {
|
|
2645
|
+
if (Array.isArray(value)) {
|
|
2646
|
+
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
2647
|
+
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
2648
|
+
if (isMatrix) {
|
|
2649
|
+
return trimmed;
|
|
2040
2650
|
}
|
|
2651
|
+
return trimmed.map(
|
|
2652
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
2653
|
+
);
|
|
2041
2654
|
}
|
|
2042
|
-
|
|
2043
|
-
return
|
|
2044
|
-
|
|
2045
|
-
|
|
2655
|
+
if (value && typeof value === "object") {
|
|
2656
|
+
return Object.fromEntries(
|
|
2657
|
+
Object.entries(value).map(([key, nested]) => [
|
|
2658
|
+
key,
|
|
2659
|
+
trimSeriesDeep(nested)
|
|
2660
|
+
])
|
|
2046
2661
|
);
|
|
2047
|
-
} catch (error) {
|
|
2048
|
-
if (isTsModulePath(modulePath)) {
|
|
2049
|
-
await ensureTsNodeRegistered();
|
|
2050
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2051
|
-
return requireFn(modulePath);
|
|
2052
|
-
}
|
|
2053
|
-
throw error;
|
|
2054
2662
|
}
|
|
2663
|
+
return value;
|
|
2055
2664
|
};
|
|
2056
|
-
var
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
};
|
|
2060
|
-
var findConfigFilePath = (cwd) => {
|
|
2061
|
-
let currentDir = import_path.default.resolve(cwd);
|
|
2062
|
-
while (true) {
|
|
2063
|
-
for (const fileName of CONFIG_FILE_NAMES) {
|
|
2064
|
-
const fullPath = import_path.default.join(currentDir, fileName);
|
|
2065
|
-
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
2066
|
-
return fullPath;
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
const parentDir = import_path.default.dirname(currentDir);
|
|
2070
|
-
if (parentDir === currentDir) {
|
|
2071
|
-
return null;
|
|
2072
|
-
}
|
|
2073
|
-
currentDir = parentDir;
|
|
2665
|
+
var buildCompactAiIndicatorsSnapshot = (value) => {
|
|
2666
|
+
const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
|
|
2667
|
+
if (typeof compactSnapshot === "function") {
|
|
2668
|
+
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
2074
2669
|
}
|
|
2670
|
+
return trimSeriesDeep(value);
|
|
2075
2671
|
};
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
if (normalized.startsWith("file://")) {
|
|
2082
|
-
try {
|
|
2083
|
-
return (0, import_url.fileURLToPath)(normalized);
|
|
2084
|
-
} catch {
|
|
2085
|
-
return normalized;
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
if (import_path.default.isAbsolute(normalized)) {
|
|
2089
|
-
return normalized;
|
|
2090
|
-
}
|
|
2091
|
-
if (isRelativeModulePath(normalized)) {
|
|
2092
|
-
return import_path.default.resolve(cwd, normalized);
|
|
2672
|
+
|
|
2673
|
+
// src/aiMarketContext.ts
|
|
2674
|
+
var toRecord = (value) => {
|
|
2675
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2676
|
+
return null;
|
|
2093
2677
|
}
|
|
2094
|
-
return
|
|
2678
|
+
return value;
|
|
2095
2679
|
};
|
|
2096
|
-
var
|
|
2097
|
-
const
|
|
2098
|
-
|
|
2099
|
-
return cached;
|
|
2100
|
-
}
|
|
2101
|
-
const configFilePath = findConfigFilePath(cwd);
|
|
2102
|
-
if (!configFilePath) {
|
|
2103
|
-
cachedByCwd.set(cwd, {});
|
|
2104
|
-
return {};
|
|
2105
|
-
}
|
|
2106
|
-
try {
|
|
2107
|
-
const moduleExports = await importConfigFile(configFilePath);
|
|
2108
|
-
const config = resolveExportedConfig(moduleExports);
|
|
2109
|
-
cachedByCwd.set(cwd, config);
|
|
2110
|
-
if (!announcedConfigFile.has(configFilePath)) {
|
|
2111
|
-
announcedConfigFile.add(configFilePath);
|
|
2112
|
-
import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
2113
|
-
}
|
|
2114
|
-
return config;
|
|
2115
|
-
} catch (error) {
|
|
2116
|
-
import_logger2.logger.log(
|
|
2117
|
-
"warn",
|
|
2118
|
-
"Failed to load TradeJS config from %s: %s",
|
|
2119
|
-
configFilePath,
|
|
2120
|
-
String(error)
|
|
2121
|
-
);
|
|
2122
|
-
cachedByCwd.set(cwd, {});
|
|
2123
|
-
return {};
|
|
2124
|
-
}
|
|
2680
|
+
var toFiniteNumber = (value) => {
|
|
2681
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
2682
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
2125
2683
|
};
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
var
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2684
|
+
var roundTo = (value, decimals) => {
|
|
2685
|
+
const factor = 10 ** decimals;
|
|
2686
|
+
return Math.round(value * factor) / factor;
|
|
2687
|
+
};
|
|
2688
|
+
var buildMissingSpreadContext = () => ({
|
|
2689
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2690
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2691
|
+
available: false,
|
|
2692
|
+
value: null,
|
|
2693
|
+
zScore: null,
|
|
2694
|
+
bps: null,
|
|
2695
|
+
absBps: null,
|
|
2696
|
+
bias: null,
|
|
2697
|
+
severity: null
|
|
2140
2698
|
});
|
|
2141
|
-
var
|
|
2142
|
-
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2699
|
+
var buildSpreadContextFromSignal = (signal) => {
|
|
2700
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2701
|
+
const relative = toRecord(baseContext?.relative);
|
|
2702
|
+
const execution = toRecord(relative?.execution);
|
|
2703
|
+
const spread = toFiniteNumber(execution?.venueSpread);
|
|
2704
|
+
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
2705
|
+
if (spread == null) {
|
|
2706
|
+
return buildMissingSpreadContext();
|
|
2148
2707
|
}
|
|
2708
|
+
const value = roundTo(spread, 8);
|
|
2709
|
+
const bps = roundTo(value * 1e4, 2);
|
|
2710
|
+
const absBps = Math.abs(bps);
|
|
2711
|
+
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
2712
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
2149
2713
|
return {
|
|
2150
|
-
|
|
2151
|
-
|
|
2714
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2715
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2716
|
+
available: true,
|
|
2717
|
+
value,
|
|
2718
|
+
zScore,
|
|
2719
|
+
bps,
|
|
2720
|
+
absBps,
|
|
2721
|
+
bias,
|
|
2722
|
+
severity
|
|
2152
2723
|
};
|
|
2153
2724
|
};
|
|
2154
|
-
var
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
const
|
|
2725
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
2726
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2727
|
+
const participation = toRecord(baseContext?.participation);
|
|
2728
|
+
const delta = toRecord(participation?.delta);
|
|
2729
|
+
const source = String(delta?.source ?? "");
|
|
2730
|
+
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
2731
|
+
if (!delta || !isTrueDeltaSource) {
|
|
2732
|
+
return {
|
|
2733
|
+
source: source || null,
|
|
2734
|
+
available: false,
|
|
2735
|
+
buyPressurePct: null,
|
|
2736
|
+
buyVolume: null,
|
|
2737
|
+
sellVolume: null,
|
|
2738
|
+
netDelta: null,
|
|
2739
|
+
deltaPct: null,
|
|
2740
|
+
signedVolumeZScore: null
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2159
2743
|
return {
|
|
2160
|
-
|
|
2161
|
-
|
|
2744
|
+
source,
|
|
2745
|
+
available: true,
|
|
2746
|
+
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
2747
|
+
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
2748
|
+
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
2749
|
+
netDelta: toFiniteNumber(delta.netDelta),
|
|
2750
|
+
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
2751
|
+
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
2162
2752
|
};
|
|
2163
2753
|
};
|
|
2164
|
-
var
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2754
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
2755
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2756
|
+
const participation = toRecord(baseContext?.participation);
|
|
2757
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
2758
|
+
if (!tradeFlow) {
|
|
2759
|
+
return {
|
|
2760
|
+
source: null,
|
|
2761
|
+
available: false,
|
|
2762
|
+
interval: null,
|
|
2763
|
+
stale: null,
|
|
2764
|
+
trades: null,
|
|
2765
|
+
buyPressurePct: null,
|
|
2766
|
+
netBaseDelta: null,
|
|
2767
|
+
netQuoteDelta: null
|
|
2768
|
+
};
|
|
2171
2769
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2770
|
+
return {
|
|
2771
|
+
source: String(tradeFlow.source ?? ""),
|
|
2772
|
+
available: true,
|
|
2773
|
+
interval: String(tradeFlow.interval ?? ""),
|
|
2774
|
+
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
2775
|
+
trades: toFiniteNumber(tradeFlow.trades),
|
|
2776
|
+
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
2777
|
+
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
2778
|
+
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
2779
|
+
};
|
|
2780
|
+
};
|
|
2781
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
2782
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2783
|
+
const relative = toRecord(baseContext?.relative);
|
|
2784
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
2785
|
+
if (!breadth) {
|
|
2786
|
+
return {
|
|
2787
|
+
source: null,
|
|
2788
|
+
available: false,
|
|
2789
|
+
universe: null,
|
|
2790
|
+
interval: null,
|
|
2791
|
+
stale: null,
|
|
2792
|
+
symbolsCount: null,
|
|
2793
|
+
advanceDeclineRatio: null,
|
|
2794
|
+
pctAboveMa20: null,
|
|
2795
|
+
pctAboveMa50: null,
|
|
2796
|
+
equalWeightedReturn: null,
|
|
2797
|
+
volumeWeightedReturn: null,
|
|
2798
|
+
dispersion: null
|
|
2799
|
+
};
|
|
2175
2800
|
}
|
|
2176
|
-
return
|
|
2801
|
+
return {
|
|
2802
|
+
source: String(breadth.source ?? ""),
|
|
2803
|
+
available: true,
|
|
2804
|
+
universe: String(breadth.universe ?? ""),
|
|
2805
|
+
interval: String(breadth.interval ?? ""),
|
|
2806
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2807
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2808
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2809
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2810
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2811
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2812
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2813
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2814
|
+
};
|
|
2177
2815
|
};
|
|
2178
|
-
var
|
|
2179
|
-
const
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
)
|
|
2183
|
-
|
|
2816
|
+
var buildMarketBreadthsContextFromSignal = (signal) => {
|
|
2817
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2818
|
+
const relative = toRecord(baseContext?.relative);
|
|
2819
|
+
const breadths = toRecord(relative?.marketBreadths);
|
|
2820
|
+
const build = (key) => {
|
|
2821
|
+
const breadth = toRecord(breadths?.[key]);
|
|
2822
|
+
if (!breadth) {
|
|
2823
|
+
return {
|
|
2824
|
+
source: null,
|
|
2825
|
+
available: false,
|
|
2826
|
+
universe: null,
|
|
2827
|
+
interval: null,
|
|
2828
|
+
stale: null,
|
|
2829
|
+
symbolsCount: null,
|
|
2830
|
+
advanceDeclineRatio: null,
|
|
2831
|
+
pctAboveMa20: null,
|
|
2832
|
+
pctAboveMa50: null,
|
|
2833
|
+
equalWeightedReturn: null,
|
|
2834
|
+
volumeWeightedReturn: null,
|
|
2835
|
+
dispersion: null
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
return {
|
|
2839
|
+
source: String(breadth.source ?? ""),
|
|
2840
|
+
available: true,
|
|
2841
|
+
universe: String(breadth.universe ?? ""),
|
|
2842
|
+
interval: String(breadth.interval ?? ""),
|
|
2843
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2844
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2845
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2846
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2847
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2848
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2849
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2850
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2851
|
+
};
|
|
2852
|
+
};
|
|
2853
|
+
return {
|
|
2854
|
+
top5: build("top5"),
|
|
2855
|
+
top10: build("top10"),
|
|
2856
|
+
top30: build("top30"),
|
|
2857
|
+
top50: build("top50"),
|
|
2858
|
+
top100: build("top100")
|
|
2859
|
+
};
|
|
2184
2860
|
};
|
|
2185
|
-
var
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2861
|
+
var buildTargetVsReference = (signal, asset) => {
|
|
2862
|
+
const base = toRecord(signal.additionalIndicators?.baseContext);
|
|
2863
|
+
const relative = toRecord(base?.relative);
|
|
2864
|
+
const target = toRecord(relative?.[`targetVs${asset}`]);
|
|
2865
|
+
return {
|
|
2866
|
+
source: target ? String(target.source ?? "") : null,
|
|
2867
|
+
available: Boolean(target),
|
|
2868
|
+
ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
|
|
2869
|
+
ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
|
|
2870
|
+
ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
|
|
2871
|
+
alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
|
|
2872
|
+
alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
|
|
2873
|
+
alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
|
|
2874
|
+
beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
|
|
2875
|
+
correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
|
|
2876
|
+
ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
|
|
2877
|
+
};
|
|
2191
2878
|
};
|
|
2192
|
-
var
|
|
2193
|
-
const
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
} else {
|
|
2203
|
-
names.add(strategyName);
|
|
2204
|
-
}
|
|
2205
|
-
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
2206
|
-
issues.push(`${entryPath}: defaults must be an object`);
|
|
2207
|
-
}
|
|
2208
|
-
if (typeof entry?.parseConfig !== "function") {
|
|
2209
|
-
issues.push(`${entryPath}: parseConfig is required`);
|
|
2210
|
-
}
|
|
2211
|
-
if (typeof entry?.createCore !== "function") {
|
|
2212
|
-
issues.push(`${entryPath}: createCore is required`);
|
|
2213
|
-
}
|
|
2214
|
-
});
|
|
2215
|
-
return issues;
|
|
2879
|
+
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
2880
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
|
|
2881
|
+
return {
|
|
2882
|
+
...shared,
|
|
2883
|
+
alphaVsBtc1h: alpha1h,
|
|
2884
|
+
alphaVsBtc4h: alpha4h,
|
|
2885
|
+
alphaVsBtc24h: alpha24h,
|
|
2886
|
+
betaToBtc20: beta,
|
|
2887
|
+
correlationToBtc20: correlation
|
|
2888
|
+
};
|
|
2216
2889
|
};
|
|
2217
|
-
var
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2890
|
+
var buildTargetVsEthContextFromSignal = (signal) => {
|
|
2891
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
|
|
2892
|
+
return {
|
|
2893
|
+
...shared,
|
|
2894
|
+
alphaVsEth1h: alpha1h,
|
|
2895
|
+
alphaVsEth4h: alpha4h,
|
|
2896
|
+
alphaVsEth24h: alpha24h,
|
|
2897
|
+
betaToEth20: beta,
|
|
2898
|
+
correlationToEth20: correlation
|
|
2899
|
+
};
|
|
2900
|
+
};
|
|
2901
|
+
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
2902
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2903
|
+
const relative = toRecord(baseContext?.relative);
|
|
2904
|
+
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
2905
|
+
if (!btcAltRegime) {
|
|
2906
|
+
return {
|
|
2907
|
+
source: null,
|
|
2908
|
+
available: false,
|
|
2909
|
+
universe: null,
|
|
2910
|
+
interval: null,
|
|
2911
|
+
stale: null,
|
|
2912
|
+
regime: null,
|
|
2913
|
+
btcReturn24h: null,
|
|
2914
|
+
altBasketReturn24h: null,
|
|
2915
|
+
btcVsAltReturn24h: null,
|
|
2916
|
+
btcTurnoverShare24h: null,
|
|
2917
|
+
btcTurnoverShareChange24h: null,
|
|
2918
|
+
altVolToBtcVol24h: null,
|
|
2919
|
+
altDispersion24h: null
|
|
2920
|
+
};
|
|
2224
2921
|
}
|
|
2922
|
+
return {
|
|
2923
|
+
source: String(btcAltRegime.source ?? ""),
|
|
2924
|
+
available: true,
|
|
2925
|
+
universe: String(btcAltRegime.universe ?? ""),
|
|
2926
|
+
interval: String(btcAltRegime.interval ?? ""),
|
|
2927
|
+
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
2928
|
+
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
2929
|
+
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
2930
|
+
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
2931
|
+
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
2932
|
+
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
2933
|
+
btcTurnoverShareChange24h: toFiniteNumber(
|
|
2934
|
+
btcAltRegime.btcTurnoverShareChange24h
|
|
2935
|
+
),
|
|
2936
|
+
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
2937
|
+
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
2938
|
+
};
|
|
2225
2939
|
};
|
|
2226
|
-
var
|
|
2227
|
-
|
|
2228
|
-
|
|
2940
|
+
var buildCmcGlobalContextFromSignal = (signal) => {
|
|
2941
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2942
|
+
const relative = toRecord(baseContext?.relative);
|
|
2943
|
+
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
2944
|
+
if (!cmcGlobal) {
|
|
2945
|
+
return {
|
|
2946
|
+
source: null,
|
|
2947
|
+
available: false,
|
|
2948
|
+
interval: null,
|
|
2949
|
+
asOfTs: null,
|
|
2950
|
+
stale: null,
|
|
2951
|
+
totalMarketCapUsd: null,
|
|
2952
|
+
totalVolumeUsd: null,
|
|
2953
|
+
totalVolumeReportedUsd: null,
|
|
2954
|
+
altMarketCapUsd: null,
|
|
2955
|
+
altVolumeUsd: null,
|
|
2956
|
+
altVolumeReportedUsd: null,
|
|
2957
|
+
btcDominancePct: null,
|
|
2958
|
+
ethDominancePct: null,
|
|
2959
|
+
btcDominanceChange24hPct: null,
|
|
2960
|
+
ethDominanceChange24hPct: null,
|
|
2961
|
+
altMarketCapChange24hPct: null,
|
|
2962
|
+
altVolumeChange24hPct: null,
|
|
2963
|
+
activeCryptocurrencies: null,
|
|
2964
|
+
activeExchanges: null,
|
|
2965
|
+
activeMarketPairs: null,
|
|
2966
|
+
altLiquidityRegime: null
|
|
2967
|
+
};
|
|
2229
2968
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2969
|
+
return {
|
|
2970
|
+
source: String(cmcGlobal.source ?? ""),
|
|
2971
|
+
available: true,
|
|
2972
|
+
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
2973
|
+
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
2974
|
+
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
2975
|
+
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
2976
|
+
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
2977
|
+
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
2978
|
+
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
2979
|
+
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
2980
|
+
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
2981
|
+
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
2982
|
+
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
2983
|
+
btcDominanceChange24hPct: toFiniteNumber(
|
|
2984
|
+
cmcGlobal.btcDominanceChange24hPct
|
|
2985
|
+
),
|
|
2986
|
+
ethDominanceChange24hPct: toFiniteNumber(
|
|
2987
|
+
cmcGlobal.ethDominanceChange24hPct
|
|
2988
|
+
),
|
|
2989
|
+
altMarketCapChange24hPct: toFiniteNumber(
|
|
2990
|
+
cmcGlobal.altMarketCapChange24hPct
|
|
2991
|
+
),
|
|
2992
|
+
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
2993
|
+
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
2994
|
+
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
2995
|
+
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
2996
|
+
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
2997
|
+
};
|
|
2244
2998
|
};
|
|
2245
|
-
var
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2999
|
+
var buildCmcReferenceAssetsContextFromSignal = (signal) => {
|
|
3000
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3001
|
+
const relative = toRecord(baseContext?.relative);
|
|
3002
|
+
const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
|
|
3003
|
+
if (!cmcReferenceAssets) {
|
|
3004
|
+
return {
|
|
3005
|
+
source: null,
|
|
3006
|
+
available: false,
|
|
3007
|
+
interval: null,
|
|
3008
|
+
asOfTs: null,
|
|
3009
|
+
stale: null,
|
|
3010
|
+
btcMarketCapUsd: null,
|
|
3011
|
+
ethMarketCapUsd: null,
|
|
3012
|
+
btcVolumeUsd: null,
|
|
3013
|
+
ethVolumeUsd: null,
|
|
3014
|
+
btcVolumeToMarketCap: null,
|
|
3015
|
+
ethVolumeToMarketCap: null,
|
|
3016
|
+
ethBtcMarketCapRatio: null,
|
|
3017
|
+
ethBtcMarketCapRatioChange24hPct: null,
|
|
3018
|
+
ethVsBtcVolumeRatio: null,
|
|
3019
|
+
referenceLiquidityRegime: null
|
|
3020
|
+
};
|
|
2251
3021
|
}
|
|
3022
|
+
return {
|
|
3023
|
+
source: String(cmcReferenceAssets.source ?? ""),
|
|
3024
|
+
available: true,
|
|
3025
|
+
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
3026
|
+
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
3027
|
+
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
3028
|
+
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
3029
|
+
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
3030
|
+
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
3031
|
+
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
3032
|
+
btcVolumeToMarketCap: toFiniteNumber(
|
|
3033
|
+
cmcReferenceAssets.btcVolumeToMarketCap
|
|
3034
|
+
),
|
|
3035
|
+
ethVolumeToMarketCap: toFiniteNumber(
|
|
3036
|
+
cmcReferenceAssets.ethVolumeToMarketCap
|
|
3037
|
+
),
|
|
3038
|
+
ethBtcMarketCapRatio: toFiniteNumber(
|
|
3039
|
+
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
3040
|
+
),
|
|
3041
|
+
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
3042
|
+
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
3043
|
+
),
|
|
3044
|
+
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
3045
|
+
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
3046
|
+
};
|
|
2252
3047
|
};
|
|
2253
|
-
var
|
|
2254
|
-
|
|
2255
|
-
|
|
3048
|
+
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
3049
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3050
|
+
const relative = toRecord(baseContext?.relative);
|
|
3051
|
+
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
3052
|
+
if (!cmcExchangeLiquidity) {
|
|
3053
|
+
return {
|
|
3054
|
+
source: null,
|
|
3055
|
+
available: false,
|
|
3056
|
+
interval: null,
|
|
3057
|
+
asOfTs: null,
|
|
3058
|
+
stale: null,
|
|
3059
|
+
exchangesCount: null,
|
|
3060
|
+
totalVolumeUsd: null,
|
|
3061
|
+
totalVolumeChange24hPct: null,
|
|
3062
|
+
binanceVolumeUsd: null,
|
|
3063
|
+
binanceVolumeShare: null,
|
|
3064
|
+
topExchangeVolumeShare: null,
|
|
3065
|
+
liquidityRegime: null
|
|
3066
|
+
};
|
|
2256
3067
|
}
|
|
2257
|
-
return
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
3068
|
+
return {
|
|
3069
|
+
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
3070
|
+
available: true,
|
|
3071
|
+
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
3072
|
+
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
3073
|
+
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
3074
|
+
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
3075
|
+
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
3076
|
+
totalVolumeChange24hPct: toFiniteNumber(
|
|
3077
|
+
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
3078
|
+
),
|
|
3079
|
+
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
3080
|
+
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
3081
|
+
topExchangeVolumeShare: toFiniteNumber(
|
|
3082
|
+
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
3083
|
+
),
|
|
3084
|
+
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
3085
|
+
};
|
|
2261
3086
|
};
|
|
2262
|
-
var
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2268
|
-
const source = moduleName.trim();
|
|
2269
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
2270
|
-
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
2271
|
-
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
2272
|
-
if (cached) return cached;
|
|
2273
|
-
const resolution = (async () => {
|
|
2274
|
-
const moduleExport = await importStrategyPluginModule(
|
|
2275
|
-
resolvedModuleName,
|
|
2276
|
-
projectRoot
|
|
2277
|
-
);
|
|
2278
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2279
|
-
if (!pluginDefinition) {
|
|
2280
|
-
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
2281
|
-
}
|
|
2282
|
-
const issues = validateStrategyEntries(
|
|
2283
|
-
source,
|
|
2284
|
-
pluginDefinition.strategyEntries
|
|
2285
|
-
);
|
|
2286
|
-
if (issues.length > 0) {
|
|
2287
|
-
throw new Error(
|
|
2288
|
-
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
2289
|
-
);
|
|
2290
|
-
}
|
|
2291
|
-
const entry = pluginDefinition.strategyEntries.find(
|
|
2292
|
-
(candidate) => candidate.manifest.name === strategyName
|
|
2293
|
-
);
|
|
2294
|
-
if (!entry) {
|
|
2295
|
-
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
2296
|
-
}
|
|
2297
|
-
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
2298
|
-
if (!factory) {
|
|
2299
|
-
throw new Error("Strategy runtime factory is not configured");
|
|
2300
|
-
}
|
|
2301
|
-
const manifestsByName = new Map(
|
|
2302
|
-
pluginDefinition.strategyEntries.map(
|
|
2303
|
-
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
2304
|
-
)
|
|
2305
|
-
);
|
|
3087
|
+
var buildCmcFearGreedContextFromSignal = (signal) => {
|
|
3088
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3089
|
+
const relative = toRecord(baseContext?.relative);
|
|
3090
|
+
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
3091
|
+
if (!cmcFearGreed) {
|
|
2306
3092
|
return {
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
source
|
|
3093
|
+
source: null,
|
|
3094
|
+
available: false,
|
|
3095
|
+
interval: null,
|
|
3096
|
+
asOfTs: null,
|
|
3097
|
+
stale: null,
|
|
3098
|
+
value: null,
|
|
3099
|
+
valueChange24h: null,
|
|
3100
|
+
valueChange7d: null,
|
|
3101
|
+
classification: null,
|
|
3102
|
+
sentimentRegime: null
|
|
2318
3103
|
};
|
|
2319
|
-
})();
|
|
2320
|
-
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
2321
|
-
try {
|
|
2322
|
-
return await resolution;
|
|
2323
|
-
} catch (error) {
|
|
2324
|
-
state.strategyModuleResolutions.delete(cacheKey);
|
|
2325
|
-
throw error;
|
|
2326
|
-
}
|
|
2327
|
-
};
|
|
2328
|
-
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
2329
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2330
|
-
if (!state.pluginsLoadPromise) {
|
|
2331
|
-
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
2332
|
-
state.pluginsLoadPromise = (async () => {
|
|
2333
|
-
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
2334
|
-
const strategySet = new Set(strategyModules);
|
|
2335
|
-
const indicatorSet = new Set(indicatorModules);
|
|
2336
|
-
const pluginModuleNames = [
|
|
2337
|
-
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
2338
|
-
];
|
|
2339
|
-
if (!pluginModuleNames.length) {
|
|
2340
|
-
return;
|
|
2341
|
-
}
|
|
2342
|
-
const issues = [];
|
|
2343
|
-
for (const moduleName of pluginModuleNames) {
|
|
2344
|
-
try {
|
|
2345
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
2346
|
-
moduleName,
|
|
2347
|
-
projectRoot
|
|
2348
|
-
);
|
|
2349
|
-
const moduleExport = await importStrategyPluginModule(
|
|
2350
|
-
resolvedModuleName,
|
|
2351
|
-
projectRoot
|
|
2352
|
-
);
|
|
2353
|
-
if (strategySet.has(moduleName)) {
|
|
2354
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2355
|
-
if (!pluginDefinition) {
|
|
2356
|
-
issues.push(
|
|
2357
|
-
`${moduleName}: export { strategyEntries } is missing`
|
|
2358
|
-
);
|
|
2359
|
-
} else {
|
|
2360
|
-
const entryIssues = validateStrategyEntries(
|
|
2361
|
-
moduleName,
|
|
2362
|
-
pluginDefinition.strategyEntries,
|
|
2363
|
-
state
|
|
2364
|
-
);
|
|
2365
|
-
issues.push(...entryIssues);
|
|
2366
|
-
if (entryIssues.length === 0) {
|
|
2367
|
-
registerEntries(
|
|
2368
|
-
pluginDefinition.strategyEntries,
|
|
2369
|
-
moduleName,
|
|
2370
|
-
state
|
|
2371
|
-
);
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
if (indicatorSet.has(moduleName)) {
|
|
2376
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
2377
|
-
if (!indicatorPluginDefinition) {
|
|
2378
|
-
issues.push(
|
|
2379
|
-
`${moduleName}: export { indicatorEntries } is missing`
|
|
2380
|
-
);
|
|
2381
|
-
} else {
|
|
2382
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
2383
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
2384
|
-
moduleName,
|
|
2385
|
-
projectRoot
|
|
2386
|
-
);
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
2390
|
-
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2391
|
-
}
|
|
2392
|
-
} catch (error) {
|
|
2393
|
-
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
if (issues.length > 0) {
|
|
2397
|
-
throw new Error(
|
|
2398
|
-
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2399
|
-
);
|
|
2400
|
-
}
|
|
2401
|
-
})();
|
|
2402
3104
|
}
|
|
2403
|
-
|
|
3105
|
+
return {
|
|
3106
|
+
source: String(cmcFearGreed.source ?? ""),
|
|
3107
|
+
available: true,
|
|
3108
|
+
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
3109
|
+
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
3110
|
+
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
3111
|
+
value: toFiniteNumber(cmcFearGreed.value),
|
|
3112
|
+
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
3113
|
+
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
3114
|
+
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
3115
|
+
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
3116
|
+
};
|
|
2404
3117
|
};
|
|
2405
|
-
var
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
3118
|
+
var buildCmcIndexesContextFromSignal = (signal) => {
|
|
3119
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3120
|
+
const relative = toRecord(baseContext?.relative);
|
|
3121
|
+
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
3122
|
+
if (!cmcIndexes) {
|
|
3123
|
+
return {
|
|
3124
|
+
source: null,
|
|
3125
|
+
available: false,
|
|
3126
|
+
interval: null,
|
|
3127
|
+
asOfTs: null,
|
|
3128
|
+
stale: null,
|
|
3129
|
+
cmc100Value: null,
|
|
3130
|
+
cmc100Change24hPct: null,
|
|
3131
|
+
cmc100TopConstituentSymbol: null,
|
|
3132
|
+
cmc100TopConstituentWeightPct: null,
|
|
3133
|
+
cmc20Value: null,
|
|
3134
|
+
cmc20Change24hPct: null,
|
|
3135
|
+
cmc20TopConstituentSymbol: null,
|
|
3136
|
+
cmc20TopConstituentWeightPct: null,
|
|
3137
|
+
cmc20ToCmc100Ratio: null,
|
|
3138
|
+
cmc20ToCmc100RatioChange24hPct: null,
|
|
3139
|
+
indexRegime: null
|
|
3140
|
+
};
|
|
2409
3141
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
3142
|
+
return {
|
|
3143
|
+
source: String(cmcIndexes.source ?? ""),
|
|
3144
|
+
available: true,
|
|
3145
|
+
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
3146
|
+
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
3147
|
+
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
3148
|
+
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
3149
|
+
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
3150
|
+
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
3151
|
+
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
3152
|
+
cmcIndexes.cmc100TopConstituentWeightPct
|
|
3153
|
+
),
|
|
3154
|
+
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
3155
|
+
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
3156
|
+
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
3157
|
+
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
3158
|
+
cmcIndexes.cmc20TopConstituentWeightPct
|
|
3159
|
+
),
|
|
3160
|
+
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
3161
|
+
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
3162
|
+
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
3163
|
+
),
|
|
3164
|
+
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
3165
|
+
};
|
|
2413
3166
|
};
|
|
2414
|
-
var
|
|
2415
|
-
|
|
2416
|
-
|
|
3167
|
+
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
3168
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3169
|
+
const relative = toRecord(baseContext?.relative);
|
|
3170
|
+
const refs = toRecord(relative?.referenceTradeFlow);
|
|
3171
|
+
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
3172
|
+
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
3173
|
+
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
3174
|
+
if (!refs) {
|
|
3175
|
+
return {
|
|
3176
|
+
source: null,
|
|
3177
|
+
available: false,
|
|
3178
|
+
primaryReferenceSymbol: null,
|
|
3179
|
+
referenceSymbols: [],
|
|
3180
|
+
primaryTradeFlowBuyPressurePct: null,
|
|
3181
|
+
primaryTradeFlowStale: null
|
|
3182
|
+
};
|
|
2417
3183
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
3184
|
+
return {
|
|
3185
|
+
source: String(refs.source ?? ""),
|
|
3186
|
+
available: true,
|
|
3187
|
+
primaryReferenceSymbol,
|
|
3188
|
+
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
3189
|
+
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
3190
|
+
primaryTradeFlow?.buyPressurePct
|
|
3191
|
+
),
|
|
3192
|
+
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
3193
|
+
};
|
|
2420
3194
|
};
|
|
2421
|
-
var
|
|
2422
|
-
{
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
3195
|
+
var buildAiMarketContext = (signal) => ({
|
|
3196
|
+
execution: {
|
|
3197
|
+
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
3198
|
+
},
|
|
3199
|
+
participation: {
|
|
3200
|
+
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
3201
|
+
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
3202
|
+
},
|
|
3203
|
+
relative: {
|
|
3204
|
+
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
3205
|
+
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
3206
|
+
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
3207
|
+
targetVsEth: buildTargetVsEthContextFromSignal(signal),
|
|
3208
|
+
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
3209
|
+
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
3210
|
+
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
3211
|
+
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
3212
|
+
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
3213
|
+
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
3214
|
+
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
2437
3215
|
}
|
|
2438
|
-
);
|
|
3216
|
+
});
|
|
2439
3217
|
|
|
2440
3218
|
// src/strategy/policyProfiles.ts
|
|
2441
3219
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -2537,12 +3315,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
2537
3315
|
};
|
|
2538
3316
|
};
|
|
2539
3317
|
var defaultAiAdapter = {};
|
|
3318
|
+
var tickerProfileFeatureIds = new Set(
|
|
3319
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
3320
|
+
);
|
|
2540
3321
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
2541
3322
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
2542
3323
|
var buildAiPayloadByStrategy = (signal) => {
|
|
2543
3324
|
const basePayload = buildBaseAiPayload(signal);
|
|
2544
3325
|
const adapter = getSignalAiAdapter(signal);
|
|
2545
|
-
|
|
3326
|
+
const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
3327
|
+
const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
|
|
3328
|
+
const frozen = signal.aiFeatureReplay?.snapshot;
|
|
3329
|
+
if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
|
|
3330
|
+
const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
|
|
3331
|
+
const hasProfile = Boolean(
|
|
3332
|
+
tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
|
|
3333
|
+
);
|
|
3334
|
+
const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
|
|
3335
|
+
getTradejsProjectCwd()
|
|
3336
|
+
).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
|
|
3337
|
+
const hasPortfolio = Boolean(
|
|
3338
|
+
signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
|
|
3339
|
+
({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
|
|
3340
|
+
)
|
|
3341
|
+
);
|
|
3342
|
+
const definitions = [
|
|
3343
|
+
...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
|
|
3344
|
+
...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
|
|
3345
|
+
...indicatorFeatures,
|
|
3346
|
+
...custom
|
|
3347
|
+
];
|
|
3348
|
+
if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
|
|
3349
|
+
(0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
|
|
3350
|
+
if (signal.aiFeatureReplay && !frozen) {
|
|
3351
|
+
throw new Error(
|
|
3352
|
+
"Dataset has no frozen AI features; regenerate the decision-time export"
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3355
|
+
if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
|
|
3356
|
+
throw new Error(
|
|
3357
|
+
"AI feature definition changed; regenerate the decision-time export"
|
|
3358
|
+
);
|
|
3359
|
+
}
|
|
3360
|
+
return {
|
|
3361
|
+
...payload,
|
|
3362
|
+
featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
|
|
3363
|
+
signal: tickerProfile ? {
|
|
3364
|
+
...signal,
|
|
3365
|
+
additionalIndicators: {
|
|
3366
|
+
...signal.additionalIndicators,
|
|
3367
|
+
tickerProfile
|
|
3368
|
+
}
|
|
3369
|
+
} : signal,
|
|
3370
|
+
payload
|
|
3371
|
+
})
|
|
3372
|
+
};
|
|
2546
3373
|
};
|
|
2547
3374
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
2548
3375
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -2931,377 +3758,129 @@ var getDeterministicAiGateContext = (payload) => {
|
|
|
2931
3758
|
additionalIndicators,
|
|
2932
3759
|
...Object.values(additionalIndicators ?? {}).map(asRecord)
|
|
2933
3760
|
].filter((value) => Boolean(value));
|
|
2934
|
-
return candidates.find(
|
|
2935
|
-
(candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
|
|
2936
|
-
) ?? null;
|
|
2937
|
-
};
|
|
2938
|
-
var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
|
|
2939
|
-
Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
|
|
2940
|
-
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.
|
|
2941
|
-
|
|
2942
|
-
Trade payload:
|
|
2943
|
-
${JSON.stringify(payload)}
|
|
2944
|
-
${buildAiHumanPromptAddonByStrategy(signal, payload)}
|
|
2945
|
-
`;
|
|
2946
|
-
var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
2947
|
-
const settings = await getAiUserSettings(options.userName);
|
|
2948
|
-
const responseLanguage = (0, import_aiLanguages2.getAiResponseLanguagePromptName)(
|
|
2949
|
-
settings.AI_RESPONSE_LANGUAGE || import_aiLanguages2.DEFAULT_AI_RESPONSE_LANGUAGE
|
|
2950
|
-
);
|
|
2951
|
-
const response = await invokeAiPromptChat({
|
|
2952
|
-
userName: options.userName,
|
|
2953
|
-
model: options.model,
|
|
2954
|
-
messages: [
|
|
2955
|
-
{ role: "system", content: systemPrompt },
|
|
2956
|
-
{
|
|
2957
|
-
role: "system",
|
|
2958
|
-
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
2959
|
-
},
|
|
2960
|
-
{ role: "user", content: humanPrompt }
|
|
2961
|
-
]
|
|
2962
|
-
});
|
|
2963
|
-
const parsed = parseAIResponse(response.content);
|
|
2964
|
-
const normalized = normalizeAnalysis(parsed);
|
|
2965
|
-
if (!options.signal) {
|
|
2966
|
-
return normalized;
|
|
2967
|
-
}
|
|
2968
|
-
return postProcessAiAnalysisByStrategy(
|
|
2969
|
-
options.signal,
|
|
2970
|
-
normalized,
|
|
2971
|
-
options.payload
|
|
2972
|
-
);
|
|
2973
|
-
};
|
|
2974
|
-
var runAiPromptLocal = async (signal, options = {}) => {
|
|
2975
|
-
const payload = options.payload ?? buildAiPayload(signal);
|
|
2976
|
-
const gateContext = getDeterministicAiGateContext(payload);
|
|
2977
|
-
const signalDirection = getSignalDirection(signal);
|
|
2978
|
-
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
2979
|
-
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
2980
|
-
return postProcessLocalAiAnalysisByStrategy(
|
|
2981
|
-
signal,
|
|
2982
|
-
{
|
|
2983
|
-
direction: approvalAllowedNow ? signalDirection : null,
|
|
2984
|
-
quality: deterministicQuality,
|
|
2985
|
-
needRetest: !approvalAllowedNow,
|
|
2986
|
-
retestPrice: null,
|
|
2987
|
-
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
2988
|
-
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
2989
|
-
},
|
|
2990
|
-
payload
|
|
2991
|
-
);
|
|
2992
|
-
};
|
|
2993
|
-
var askAI = async (signal, options = {}) => {
|
|
2994
|
-
const { symbol } = signal;
|
|
2995
|
-
const payload = buildAiPayload(signal);
|
|
2996
|
-
const content = await runAiPrompt(
|
|
2997
|
-
{
|
|
2998
|
-
systemPrompt: buildAiSystemPrompt(signal),
|
|
2999
|
-
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
3000
|
-
},
|
|
3001
|
-
{
|
|
3002
|
-
...options,
|
|
3003
|
-
signal,
|
|
3004
|
-
payload
|
|
3005
|
-
}
|
|
3006
|
-
);
|
|
3007
|
-
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3008
|
-
return content;
|
|
3009
|
-
};
|
|
3010
|
-
|
|
3011
|
-
// src/strategyAdapters/ml.ts
|
|
3012
|
-
var defaultMlAdapter = {
|
|
3013
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3014
|
-
};
|
|
3015
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3016
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3017
|
-
getStrategyManifest(strategy),
|
|
3018
|
-
profileId
|
|
3019
|
-
);
|
|
3020
|
-
if (!strategyAdapter) return defaultMlAdapter;
|
|
3021
|
-
return {
|
|
3022
|
-
...defaultMlAdapter,
|
|
3023
|
-
...strategyAdapter
|
|
3024
|
-
};
|
|
3025
|
-
};
|
|
3026
|
-
|
|
3027
|
-
// src/mlPayload.ts
|
|
3028
|
-
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3029
|
-
return getStrategyMlAdapter(
|
|
3030
|
-
strategyName,
|
|
3031
|
-
profileId
|
|
3032
|
-
).normalizeStrategyConfig?.(strategyConfig);
|
|
3033
|
-
};
|
|
3034
|
-
var buildMlPayload = (payload) => {
|
|
3035
|
-
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3036
|
-
const profileId = payload.signal?.policyProfileId;
|
|
3037
|
-
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3038
|
-
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3039
|
-
const nextSignal = {
|
|
3040
|
-
...normalizedSignal,
|
|
3041
|
-
indicators: {
|
|
3042
|
-
...normalizedSignal?.indicators ?? {}
|
|
3043
|
-
}
|
|
3044
|
-
};
|
|
3045
|
-
const nextContext = payload.context ? {
|
|
3046
|
-
...payload.context,
|
|
3047
|
-
strategyConfig: normalizeStrategyConfig(
|
|
3048
|
-
payload.context.strategyConfig,
|
|
3049
|
-
strategyName,
|
|
3050
|
-
profileId
|
|
3051
|
-
)
|
|
3052
|
-
} : void 0;
|
|
3053
|
-
return {
|
|
3054
|
-
signal: nextSignal,
|
|
3055
|
-
context: nextContext
|
|
3056
|
-
};
|
|
3057
|
-
};
|
|
3058
|
-
|
|
3059
|
-
// src/runtimeJournal.ts
|
|
3060
|
-
var import_node_crypto = require("crypto");
|
|
3061
|
-
var import_constants = require("@tradejs/core/constants");
|
|
3062
|
-
var import_time = require("@tradejs/core/time");
|
|
3063
|
-
var import_trade = require("@tradejs/core/trade");
|
|
3064
|
-
var import_logger3 = require("@tradejs/infra/logger");
|
|
3065
|
-
var import_redis2 = require("@tradejs/infra/redis");
|
|
3066
|
-
var now = () => Date.now();
|
|
3067
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
3068
|
-
var calculateClosedPnl = ({
|
|
3069
|
-
direction,
|
|
3070
|
-
entryPrice,
|
|
3071
|
-
exitPrice,
|
|
3072
|
-
qty
|
|
3073
|
-
}) => {
|
|
3074
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
3075
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
3076
|
-
};
|
|
3077
|
-
var createRuntimeOrderId = (strategy) => {
|
|
3078
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
3079
|
-
if (prefix === "tjs-") {
|
|
3080
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
3081
|
-
}
|
|
3082
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
3083
|
-
};
|
|
3084
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
3085
|
-
const { userName } = params;
|
|
3086
|
-
if (!userName) {
|
|
3087
|
-
return null;
|
|
3088
|
-
}
|
|
3089
|
-
const record = {
|
|
3090
|
-
...params,
|
|
3091
|
-
entryCount: 1,
|
|
3092
|
-
lastEntryPrice: params.entryPrice,
|
|
3093
|
-
lastEntryQty: params.qty,
|
|
3094
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
3095
|
-
status: "active",
|
|
3096
|
-
currentPrice: params.entryPrice,
|
|
3097
|
-
currentPnl: 0,
|
|
3098
|
-
closedPnl: null,
|
|
3099
|
-
exitPrice: null,
|
|
3100
|
-
exitTimestamp: null,
|
|
3101
|
-
lastSyncedAt: now()
|
|
3102
|
-
};
|
|
3103
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
3104
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
3105
|
-
try {
|
|
3106
|
-
await Promise.all([
|
|
3107
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
3108
|
-
expire: 0
|
|
3109
|
-
}),
|
|
3110
|
-
(0, import_redis2.setHashJsonField)(
|
|
3111
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3112
|
-
record.orderId,
|
|
3113
|
-
record,
|
|
3114
|
-
{ expire: 0 }
|
|
3115
|
-
),
|
|
3116
|
-
(0, import_redis2.setData)(
|
|
3117
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
3118
|
-
{ orderId: record.orderId },
|
|
3119
|
-
{ expire: 0 }
|
|
3120
|
-
)
|
|
3121
|
-
]);
|
|
3122
|
-
} catch (error) {
|
|
3123
|
-
import_logger3.logger.error(
|
|
3124
|
-
"runtime trade open journal failed: %s %s",
|
|
3125
|
-
record.symbol,
|
|
3126
|
-
error?.message || String(error)
|
|
3127
|
-
);
|
|
3128
|
-
}
|
|
3129
|
-
return record;
|
|
3130
|
-
};
|
|
3131
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
3132
|
-
const {
|
|
3133
|
-
userName,
|
|
3134
|
-
strategy,
|
|
3135
|
-
symbol,
|
|
3136
|
-
direction,
|
|
3137
|
-
resultingQty,
|
|
3138
|
-
resultingEntryPrice,
|
|
3139
|
-
addedQty,
|
|
3140
|
-
addedEntryPrice,
|
|
3141
|
-
entryTimestamp,
|
|
3142
|
-
fee,
|
|
3143
|
-
accountId,
|
|
3144
|
-
deploymentId
|
|
3145
|
-
} = params;
|
|
3146
|
-
if (!userName) {
|
|
3147
|
-
return null;
|
|
3148
|
-
}
|
|
3149
|
-
const existing = await getActiveRuntimeTrade({
|
|
3150
|
-
userName,
|
|
3151
|
-
symbol,
|
|
3152
|
-
accountId,
|
|
3153
|
-
deploymentId
|
|
3154
|
-
});
|
|
3155
|
-
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
3156
|
-
return null;
|
|
3157
|
-
}
|
|
3158
|
-
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
3159
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
3160
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3161
|
-
const next = {
|
|
3162
|
-
...existing,
|
|
3163
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3164
|
-
qty: resultingQty,
|
|
3165
|
-
entryPrice: resultingEntryPrice,
|
|
3166
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3167
|
-
lastEntryPrice: addedEntryPrice,
|
|
3168
|
-
lastEntryQty: addedQty,
|
|
3169
|
-
lastEntryTimestamp: entryTimestamp,
|
|
3170
|
-
currentPrice: resultingEntryPrice,
|
|
3171
|
-
currentPnl: 0,
|
|
3172
|
-
fee: openFee,
|
|
3173
|
-
openFee,
|
|
3174
|
-
totalFee,
|
|
3175
|
-
lastSyncedAt: now()
|
|
3176
|
-
};
|
|
3177
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3178
|
-
try {
|
|
3179
|
-
await Promise.all([
|
|
3180
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3181
|
-
expire: 0
|
|
3182
|
-
}),
|
|
3183
|
-
(0, import_redis2.setHashJsonField)(
|
|
3184
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3185
|
-
existing.orderId,
|
|
3186
|
-
next,
|
|
3187
|
-
{ expire: 0 }
|
|
3188
|
-
)
|
|
3189
|
-
]);
|
|
3190
|
-
} catch (error) {
|
|
3191
|
-
import_logger3.logger.error(
|
|
3192
|
-
"runtime trade increase journal failed: %s %s",
|
|
3193
|
-
symbol,
|
|
3194
|
-
error?.message || String(error)
|
|
3195
|
-
);
|
|
3196
|
-
}
|
|
3197
|
-
return next;
|
|
3761
|
+
return candidates.find(
|
|
3762
|
+
(candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
|
|
3763
|
+
) ?? null;
|
|
3198
3764
|
};
|
|
3199
|
-
var
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3765
|
+
var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
|
|
3766
|
+
Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
|
|
3767
|
+
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.
|
|
3768
|
+
|
|
3769
|
+
Trade payload:
|
|
3770
|
+
${JSON.stringify(payload)}
|
|
3771
|
+
${buildAiHumanPromptAddonByStrategy(signal, payload)}
|
|
3772
|
+
`;
|
|
3773
|
+
var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
3774
|
+
const settings = await getAiUserSettings(options.userName);
|
|
3775
|
+
const responseLanguage = (0, import_aiLanguages2.getAiResponseLanguagePromptName)(
|
|
3776
|
+
settings.AI_RESPONSE_LANGUAGE || import_aiLanguages2.DEFAULT_AI_RESPONSE_LANGUAGE
|
|
3207
3777
|
);
|
|
3208
|
-
const
|
|
3209
|
-
|
|
3210
|
-
|
|
3778
|
+
const response = await invokeAiPromptChat({
|
|
3779
|
+
userName: options.userName,
|
|
3780
|
+
model: options.model,
|
|
3781
|
+
messages: [
|
|
3782
|
+
{ role: "system", content: systemPrompt },
|
|
3783
|
+
{
|
|
3784
|
+
role: "system",
|
|
3785
|
+
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
3786
|
+
},
|
|
3787
|
+
{ role: "user", content: humanPrompt }
|
|
3788
|
+
]
|
|
3789
|
+
});
|
|
3790
|
+
const parsed = parseAIResponse(response.content);
|
|
3791
|
+
const normalized = normalizeAnalysis(parsed);
|
|
3792
|
+
if (!options.signal) {
|
|
3793
|
+
return normalized;
|
|
3211
3794
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3795
|
+
return postProcessAiAnalysisByStrategy(
|
|
3796
|
+
options.signal,
|
|
3797
|
+
normalized,
|
|
3798
|
+
options.payload
|
|
3215
3799
|
);
|
|
3216
|
-
if (!existing || existing.status !== "active") {
|
|
3217
|
-
await (0, import_redis2.delKey)(
|
|
3218
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3219
|
-
);
|
|
3220
|
-
return null;
|
|
3221
|
-
}
|
|
3222
|
-
return existing;
|
|
3223
3800
|
};
|
|
3224
|
-
var
|
|
3225
|
-
const
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
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
|
-
|
|
3801
|
+
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3802
|
+
const payload = options.payload ?? buildAiPayload(signal);
|
|
3803
|
+
const gateContext = getDeterministicAiGateContext(payload);
|
|
3804
|
+
const signalDirection = getSignalDirection(signal);
|
|
3805
|
+
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3806
|
+
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
3807
|
+
return postProcessLocalAiAnalysisByStrategy(
|
|
3808
|
+
signal,
|
|
3809
|
+
{
|
|
3810
|
+
direction: approvalAllowedNow ? signalDirection : null,
|
|
3811
|
+
quality: deterministicQuality,
|
|
3812
|
+
needRetest: !approvalAllowedNow,
|
|
3813
|
+
retestPrice: null,
|
|
3814
|
+
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
3815
|
+
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
3816
|
+
},
|
|
3817
|
+
payload
|
|
3818
|
+
);
|
|
3819
|
+
};
|
|
3820
|
+
var askAI = async (signal, options = {}) => {
|
|
3821
|
+
const { symbol } = signal;
|
|
3822
|
+
const payload = buildAiPayload(signal);
|
|
3823
|
+
const content = await runAiPrompt(
|
|
3824
|
+
{
|
|
3825
|
+
systemPrompt: buildAiSystemPrompt(signal),
|
|
3826
|
+
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
3827
|
+
},
|
|
3828
|
+
{
|
|
3829
|
+
...options,
|
|
3830
|
+
signal,
|
|
3831
|
+
payload
|
|
3832
|
+
}
|
|
3833
|
+
);
|
|
3834
|
+
await (0, import_redis3.setData)(import_redis3.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3835
|
+
return content;
|
|
3836
|
+
};
|
|
3837
|
+
|
|
3838
|
+
// src/strategyAdapters/ml.ts
|
|
3839
|
+
var defaultMlAdapter = {
|
|
3840
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3841
|
+
};
|
|
3842
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3843
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3844
|
+
getStrategyManifest(strategy),
|
|
3845
|
+
profileId
|
|
3846
|
+
);
|
|
3847
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
3848
|
+
return {
|
|
3849
|
+
...defaultMlAdapter,
|
|
3850
|
+
...strategyAdapter
|
|
3851
|
+
};
|
|
3852
|
+
};
|
|
3853
|
+
|
|
3854
|
+
// src/mlPayload.ts
|
|
3855
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3856
|
+
return getStrategyMlAdapter(
|
|
3857
|
+
strategyName,
|
|
3858
|
+
profileId
|
|
3859
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
3860
|
+
};
|
|
3861
|
+
var buildMlPayload = (payload) => {
|
|
3862
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3863
|
+
const profileId = payload.signal?.policyProfileId;
|
|
3864
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3865
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3866
|
+
const nextSignal = {
|
|
3867
|
+
...normalizedSignal,
|
|
3868
|
+
indicators: {
|
|
3869
|
+
...normalizedSignal?.indicators ?? {}
|
|
3870
|
+
}
|
|
3871
|
+
};
|
|
3872
|
+
const nextContext = payload.context ? {
|
|
3873
|
+
...payload.context,
|
|
3874
|
+
strategyConfig: normalizeStrategyConfig(
|
|
3875
|
+
payload.context.strategyConfig,
|
|
3876
|
+
strategyName,
|
|
3877
|
+
profileId
|
|
3878
|
+
)
|
|
3879
|
+
} : void 0;
|
|
3880
|
+
return {
|
|
3881
|
+
signal: nextSignal,
|
|
3882
|
+
context: nextContext
|
|
3269
3883
|
};
|
|
3270
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3271
|
-
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3272
|
-
try {
|
|
3273
|
-
await Promise.all([
|
|
3274
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
3275
|
-
expire: import_constants.TTL_1M
|
|
3276
|
-
}),
|
|
3277
|
-
(0, import_redis2.setHashJsonField)(
|
|
3278
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3279
|
-
orderId,
|
|
3280
|
-
next,
|
|
3281
|
-
{ expire: import_constants.TTL_1M }
|
|
3282
|
-
),
|
|
3283
|
-
(0, import_redis2.setHashJsonField)(
|
|
3284
|
-
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3285
|
-
orderId,
|
|
3286
|
-
next,
|
|
3287
|
-
{ expire: import_constants.TTL_1M }
|
|
3288
|
-
),
|
|
3289
|
-
(0, import_redis2.delKey)(
|
|
3290
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3291
|
-
userName,
|
|
3292
|
-
symbol,
|
|
3293
|
-
deploymentId ?? accountId
|
|
3294
|
-
)
|
|
3295
|
-
)
|
|
3296
|
-
]);
|
|
3297
|
-
} catch (error) {
|
|
3298
|
-
import_logger3.logger.error(
|
|
3299
|
-
"runtime trade close journal failed: %s %s",
|
|
3300
|
-
symbol,
|
|
3301
|
-
error?.message || String(error)
|
|
3302
|
-
);
|
|
3303
|
-
}
|
|
3304
|
-
return next;
|
|
3305
3884
|
};
|
|
3306
3885
|
|
|
3307
3886
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -3313,7 +3892,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3313
3892
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3314
3893
|
|
|
3315
3894
|
// src/binanceBreadthUniverses.ts
|
|
3316
|
-
var
|
|
3895
|
+
var import_node_crypto3 = require("crypto");
|
|
3317
3896
|
|
|
3318
3897
|
// src/config/binanceBreadthUniverses.json
|
|
3319
3898
|
var binanceBreadthUniverses_default = {
|
|
@@ -3552,7 +4131,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3552
4131
|
"top50",
|
|
3553
4132
|
"top100"
|
|
3554
4133
|
];
|
|
3555
|
-
var fingerprint = (value, length) => (0,
|
|
4134
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
3556
4135
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3557
4136
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3558
4137
|
rankedSymbols,
|
|
@@ -4576,7 +5155,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4576
5155
|
};
|
|
4577
5156
|
|
|
4578
5157
|
// src/strategyHelpers/derivativesContext.ts
|
|
4579
|
-
var
|
|
5158
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4580
5159
|
var import_data2 = require("@tradejs/core/data");
|
|
4581
5160
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4582
5161
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4610,7 +5189,7 @@ var parseLookbackMs = () => {
|
|
|
4610
5189
|
};
|
|
4611
5190
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4612
5191
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4613
|
-
"1h": (0,
|
|
5192
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4614
5193
|
rows15m: rowsByInterval["15m"],
|
|
4615
5194
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4616
5195
|
})
|
|
@@ -4738,7 +5317,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4738
5317
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4739
5318
|
const lookbackMs = parseLookbackMs();
|
|
4740
5319
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4741
|
-
const derivativesEndMs = (0,
|
|
5320
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4742
5321
|
decisionTimeMs,
|
|
4743
5322
|
"15m"
|
|
4744
5323
|
);
|
|
@@ -4753,7 +5332,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4753
5332
|
});
|
|
4754
5333
|
return [
|
|
4755
5334
|
symbol,
|
|
4756
|
-
(0,
|
|
5335
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
4757
5336
|
symbol,
|
|
4758
5337
|
direction: signal.direction,
|
|
4759
5338
|
timestamp: derivativesEndMs,
|
|
@@ -4780,7 +5359,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4780
5359
|
lookbackMs,
|
|
4781
5360
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
4782
5361
|
});
|
|
4783
|
-
const context = (0,
|
|
5362
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
4784
5363
|
symbol: targetSymbol,
|
|
4785
5364
|
direction: signal.direction,
|
|
4786
5365
|
timestamp: derivativesEndMs,
|
|
@@ -4828,7 +5407,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
4828
5407
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
4829
5408
|
|
|
4830
5409
|
// src/hyperliquidWhaleUniverse.ts
|
|
4831
|
-
var
|
|
5410
|
+
var import_node_crypto4 = require("crypto");
|
|
4832
5411
|
|
|
4833
5412
|
// src/config/hyperliquidPerpUniverse.json
|
|
4834
5413
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5000,7 +5579,7 @@ var hyperliquidWhales_default = {
|
|
|
5000
5579
|
};
|
|
5001
5580
|
|
|
5002
5581
|
// src/hyperliquidWhaleUniverse.ts
|
|
5003
|
-
var fingerprint2 = (value) => (0,
|
|
5582
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5004
5583
|
var validateUnique = (values, label) => {
|
|
5005
5584
|
if (new Set(values).size !== values.length) {
|
|
5006
5585
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5487,6 +6066,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5487
6066
|
})
|
|
5488
6067
|
});
|
|
5489
6068
|
}
|
|
6069
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5490
6070
|
const results = [];
|
|
5491
6071
|
for (const stage of stages) {
|
|
5492
6072
|
if (abortSignal?.aborted) break;
|
|
@@ -5534,12 +6114,12 @@ var findReplayAiAnalysis = ({
|
|
|
5534
6114
|
direction,
|
|
5535
6115
|
ai
|
|
5536
6116
|
}) => {
|
|
5537
|
-
const
|
|
5538
|
-
if (!Array.isArray(
|
|
6117
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6118
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5539
6119
|
return void 0;
|
|
5540
6120
|
}
|
|
5541
6121
|
let best = null;
|
|
5542
|
-
for (const snapshot2 of
|
|
6122
|
+
for (const snapshot2 of snapshots2) {
|
|
5543
6123
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5544
6124
|
continue;
|
|
5545
6125
|
}
|
|
@@ -5699,11 +6279,13 @@ var executeEntryOrder = async ({
|
|
|
5699
6279
|
beforePlaceOrder,
|
|
5700
6280
|
recordRuntimeTrade = true,
|
|
5701
6281
|
leverage,
|
|
5702
|
-
executionLimits
|
|
6282
|
+
executionLimits,
|
|
6283
|
+
markPortfolioSubmitted,
|
|
6284
|
+
rejectPortfolioSubmission
|
|
5703
6285
|
}) => {
|
|
5704
6286
|
await beforePlaceOrder?.();
|
|
5705
6287
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
5706
|
-
|
|
6288
|
+
let orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
5707
6289
|
const signalTimestamp = signal.timestamp;
|
|
5708
6290
|
const signalClosePrice = currentPrice;
|
|
5709
6291
|
signal.orderId = orderId;
|
|
@@ -5772,9 +6354,16 @@ var executeEntryOrder = async ({
|
|
|
5772
6354
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
5773
6355
|
orderId,
|
|
5774
6356
|
signal,
|
|
5775
|
-
...
|
|
6357
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6358
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6359
|
+
beforeSubmit: async (context) => {
|
|
6360
|
+
await beforeSubmit?.(context);
|
|
6361
|
+
return markPortfolioSubmitted?.(context.qty);
|
|
6362
|
+
}
|
|
6363
|
+
} : {}
|
|
5776
6364
|
});
|
|
5777
6365
|
const orderAckTime = Date.now();
|
|
6366
|
+
if (markPortfolioSubmitted && signal.orderId) orderId = signal.orderId;
|
|
5778
6367
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
5779
6368
|
const currentPosition = await connector.getPosition(symbol);
|
|
5780
6369
|
const fillTime = Date.now();
|
|
@@ -6763,7 +7352,7 @@ var handleProtectDecision = async ({
|
|
|
6763
7352
|
}
|
|
6764
7353
|
return decision.code;
|
|
6765
7354
|
};
|
|
6766
|
-
var
|
|
7355
|
+
var executeAdmittedEntryDecision = async ({
|
|
6767
7356
|
connector,
|
|
6768
7357
|
symbol,
|
|
6769
7358
|
decision,
|
|
@@ -6777,7 +7366,9 @@ var executeEntryDecision = async ({
|
|
|
6777
7366
|
ai,
|
|
6778
7367
|
recordRuntimeJournal,
|
|
6779
7368
|
invokeStageHooks,
|
|
6780
|
-
notifyRuntimeError
|
|
7369
|
+
notifyRuntimeError,
|
|
7370
|
+
markPortfolioSubmitted,
|
|
7371
|
+
rejectPortfolioSubmission
|
|
6781
7372
|
}) => {
|
|
6782
7373
|
const signal = decision.signal;
|
|
6783
7374
|
const beforePlaceOrder = async () => {
|
|
@@ -6824,6 +7415,8 @@ var executeEntryDecision = async ({
|
|
|
6824
7415
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
6825
7416
|
signal,
|
|
6826
7417
|
beforePlaceOrder,
|
|
7418
|
+
markPortfolioSubmitted,
|
|
7419
|
+
rejectPortfolioSubmission,
|
|
6827
7420
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
6828
7421
|
executionLimits: resolveEntryExecutionLimits(
|
|
6829
7422
|
hookCtx.strategyConfig,
|
|
@@ -6964,6 +7557,37 @@ var executeEntryDecision = async ({
|
|
|
6964
7557
|
}
|
|
6965
7558
|
return signal ?? decision.code;
|
|
6966
7559
|
};
|
|
7560
|
+
async function executeEntryDecision(params) {
|
|
7561
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
7562
|
+
const { decision, portfolio } = params;
|
|
7563
|
+
const signal = decision.signal;
|
|
7564
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
7565
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
7566
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
7567
|
+
const result = await portfolio.submit(
|
|
7568
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
7569
|
+
{
|
|
7570
|
+
id: signal.orderId,
|
|
7571
|
+
signalId: signal.signalId,
|
|
7572
|
+
strategy: signal.strategy,
|
|
7573
|
+
symbol: params.symbol,
|
|
7574
|
+
direction: decision.entryContext.direction,
|
|
7575
|
+
qty: decision.orderPlan.qty,
|
|
7576
|
+
timestamp: decision.entryContext.timestamp
|
|
7577
|
+
},
|
|
7578
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
7579
|
+
...params,
|
|
7580
|
+
markPortfolioSubmitted,
|
|
7581
|
+
rejectPortfolioSubmission
|
|
7582
|
+
})
|
|
7583
|
+
);
|
|
7584
|
+
if (result.reason) {
|
|
7585
|
+
signal.orderStatus = "failed";
|
|
7586
|
+
signal.orderFailureReason = result.reason;
|
|
7587
|
+
return signal;
|
|
7588
|
+
}
|
|
7589
|
+
return result.value;
|
|
7590
|
+
}
|
|
6967
7591
|
|
|
6968
7592
|
// src/strategyRuntime.ts
|
|
6969
7593
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -6991,6 +7615,7 @@ var createStrategyRuntime = ({
|
|
|
6991
7615
|
};
|
|
6992
7616
|
const creator = async ({
|
|
6993
7617
|
userName,
|
|
7618
|
+
connectorName,
|
|
6994
7619
|
config: baseConfig,
|
|
6995
7620
|
symbol,
|
|
6996
7621
|
universe: requestedUniverse,
|
|
@@ -7029,6 +7654,37 @@ var createStrategyRuntime = ({
|
|
|
7029
7654
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7030
7655
|
const projectHooks = projectConfig.hooks;
|
|
7031
7656
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7657
|
+
let portfolioMarket;
|
|
7658
|
+
const portfolio = getPortfolioRuntime({
|
|
7659
|
+
symbol,
|
|
7660
|
+
canClose: async (position, intent, asOf) => {
|
|
7661
|
+
if (!portfolioMarket || portfolioMarket.candle.timestamp > asOf || !isRuntimeOrderExecutionEnabled({ config, env, connector }))
|
|
7662
|
+
return false;
|
|
7663
|
+
const gate = await authorizeClose(
|
|
7664
|
+
{
|
|
7665
|
+
kind: "exit",
|
|
7666
|
+
code: intent.reason,
|
|
7667
|
+
closePlan: {
|
|
7668
|
+
direction: position.direction,
|
|
7669
|
+
price: position.markPrice,
|
|
7670
|
+
timestamp: asOf
|
|
7671
|
+
}
|
|
7672
|
+
},
|
|
7673
|
+
portfolioMarket,
|
|
7674
|
+
position.strategy
|
|
7675
|
+
);
|
|
7676
|
+
return gate?.allow !== false;
|
|
7677
|
+
},
|
|
7678
|
+
connectorName,
|
|
7679
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7680
|
+
strategy: strategyName,
|
|
7681
|
+
connector,
|
|
7682
|
+
env,
|
|
7683
|
+
userName,
|
|
7684
|
+
accountId,
|
|
7685
|
+
deploymentId
|
|
7686
|
+
});
|
|
7687
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
7032
7688
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7033
7689
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7034
7690
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7193,7 +7849,10 @@ var createStrategyRuntime = ({
|
|
|
7193
7849
|
return invokeHook(stage, hook, params, errorContext);
|
|
7194
7850
|
};
|
|
7195
7851
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7196
|
-
const failureResult =
|
|
7852
|
+
const failureResult = {
|
|
7853
|
+
allow: false,
|
|
7854
|
+
reason: "ERROR"
|
|
7855
|
+
};
|
|
7197
7856
|
const projectResults = await invokeProjectHooks(
|
|
7198
7857
|
stage,
|
|
7199
7858
|
params,
|
|
@@ -7214,6 +7873,12 @@ var createStrategyRuntime = ({
|
|
|
7214
7873
|
failureResult
|
|
7215
7874
|
);
|
|
7216
7875
|
};
|
|
7876
|
+
const authorizeClose = (decision, market, owner = strategyName) => invokeGateHooks(
|
|
7877
|
+
"beforeClosePosition",
|
|
7878
|
+
resolveManifest(owner)?.hooks?.beforeClosePosition,
|
|
7879
|
+
{ ctx: getHookCtx(owner), market, decision },
|
|
7880
|
+
{ decision, market }
|
|
7881
|
+
);
|
|
7217
7882
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
7218
7883
|
hookCtx,
|
|
7219
7884
|
market,
|
|
@@ -7327,6 +7992,20 @@ var createStrategyRuntime = ({
|
|
|
7327
7992
|
interval: decisionInterval
|
|
7328
7993
|
}) => {
|
|
7329
7994
|
if (!baseContext) return void 0;
|
|
7995
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
7996
|
+
if (profileSource) {
|
|
7997
|
+
const profile = await loadTickerProfileContext({
|
|
7998
|
+
...profileSource,
|
|
7999
|
+
projectRoot,
|
|
8000
|
+
symbol: decisionSymbol,
|
|
8001
|
+
timestamp: candle.timestamp
|
|
8002
|
+
});
|
|
8003
|
+
if (!profile)
|
|
8004
|
+
throw new Error(
|
|
8005
|
+
"Required ticker profile is unavailable at decision time"
|
|
8006
|
+
);
|
|
8007
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
8008
|
+
}
|
|
7330
8009
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7331
8010
|
return baseContext;
|
|
7332
8011
|
}
|
|
@@ -7494,6 +8173,8 @@ var createStrategyRuntime = ({
|
|
|
7494
8173
|
runtime: pending2.runtime
|
|
7495
8174
|
});
|
|
7496
8175
|
return executeEntryDecision({
|
|
8176
|
+
portfolio,
|
|
8177
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7497
8178
|
connector,
|
|
7498
8179
|
symbol,
|
|
7499
8180
|
decision: pending2.decision,
|
|
@@ -7511,6 +8192,11 @@ var createStrategyRuntime = ({
|
|
|
7511
8192
|
});
|
|
7512
8193
|
};
|
|
7513
8194
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8195
|
+
portfolioMarket = { candle, btcCandle };
|
|
8196
|
+
if (portfolioContext)
|
|
8197
|
+
await portfolioContext(
|
|
8198
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8199
|
+
);
|
|
7514
8200
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7515
8201
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7516
8202
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7613,15 +8299,10 @@ var createStrategyRuntime = ({
|
|
|
7613
8299
|
if (!makeOrdersEnabled) {
|
|
7614
8300
|
return decision.code;
|
|
7615
8301
|
}
|
|
7616
|
-
const closeGate = await
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
ctx: decisionHookCtx,
|
|
7621
|
-
market,
|
|
7622
|
-
decision
|
|
7623
|
-
},
|
|
7624
|
-
{ decision, market }
|
|
8302
|
+
const closeGate = await authorizeClose(
|
|
8303
|
+
decision,
|
|
8304
|
+
market,
|
|
8305
|
+
decisionHookCtx.strategyName
|
|
7625
8306
|
);
|
|
7626
8307
|
if (closeGate?.allow === false) {
|
|
7627
8308
|
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
|
@@ -7681,6 +8362,13 @@ var createStrategyRuntime = ({
|
|
|
7681
8362
|
});
|
|
7682
8363
|
const signal = decision.signal;
|
|
7683
8364
|
if (signal) {
|
|
8365
|
+
if (portfolio)
|
|
8366
|
+
signal.additionalIndicators = {
|
|
8367
|
+
...signal.additionalIndicators,
|
|
8368
|
+
portfolioContext: await portfolioContext(
|
|
8369
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
8370
|
+
)
|
|
8371
|
+
};
|
|
7684
8372
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7685
8373
|
if (universe) signal.universe = universe;
|
|
7686
8374
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -7920,6 +8608,8 @@ var createStrategyRuntime = ({
|
|
|
7920
8608
|
});
|
|
7921
8609
|
}
|
|
7922
8610
|
return executeEntryDecision({
|
|
8611
|
+
portfolio,
|
|
8612
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7923
8613
|
connector,
|
|
7924
8614
|
symbol,
|
|
7925
8615
|
decision,
|
|
@@ -8702,7 +9392,7 @@ var resolveExecutionCosts = async (params) => {
|
|
|
8702
9392
|
};
|
|
8703
9393
|
|
|
8704
9394
|
// src/testConnector.ts
|
|
8705
|
-
var
|
|
9395
|
+
var import_node_crypto5 = require("crypto");
|
|
8706
9396
|
var import_constants8 = require("@tradejs/core/constants");
|
|
8707
9397
|
var import_backtest2 = require("@tradejs/core/backtest");
|
|
8708
9398
|
var import_trade2 = require("@tradejs/core/trade");
|
|
@@ -9175,7 +9865,7 @@ var createTestConnector = (connector, context) => {
|
|
|
9175
9865
|
},
|
|
9176
9866
|
kline: async (options) => connector.kline(options),
|
|
9177
9867
|
getResult: async () => {
|
|
9178
|
-
const orderLogId = (0,
|
|
9868
|
+
const orderLogId = (0, import_node_crypto5.randomUUID)().slice(-12);
|
|
9179
9869
|
const fullStat = fastMode ? (0, import_backtest2.calculateStatsFull)(positionLog) : null;
|
|
9180
9870
|
return {
|
|
9181
9871
|
stat: fullStat ? {
|
|
@@ -10591,19 +11281,19 @@ var prepareTestingData = async (params) => {
|
|
|
10591
11281
|
if (shouldLoadExecutionCandles && !cachedExecutionBtcData) {
|
|
10592
11282
|
state.btcKlineCache.set(executionBtcCacheKey, executionBtcDataRaw);
|
|
10593
11283
|
}
|
|
10594
|
-
const aligned = universe === "tradfi" ? { alignedCoinCandles: dataRaw, alignedBtcCandles: dataRaw } : (0,
|
|
11284
|
+
const aligned = universe === "tradfi" ? { alignedCoinCandles: dataRaw, alignedBtcCandles: dataRaw } : (0, import_indicators4.alignSortedCandlesByTimestamp)(dataRaw, btcDataRaw);
|
|
10595
11285
|
const { data, btcData } = filterClosedAlignedCandles(
|
|
10596
11286
|
aligned.alignedCoinCandles,
|
|
10597
11287
|
aligned.alignedBtcCandles,
|
|
10598
11288
|
interval
|
|
10599
11289
|
);
|
|
10600
|
-
const btcBinanceData = universe === "tradfi" ? [] : (0,
|
|
10601
|
-
const btcCoinbaseData = universe === "tradfi" ? [] : (0,
|
|
10602
|
-
const ethData = universe === "tradfi" ? [] : (0,
|
|
11290
|
+
const btcBinanceData = universe === "tradfi" ? [] : (0, import_indicators4.alignSortedCandlesByTimestamp)(data, btcBinanceDataRaw).alignedBtcCandles;
|
|
11291
|
+
const btcCoinbaseData = universe === "tradfi" ? [] : (0, import_indicators4.alignSortedCandlesByTimestamp)(data, btcCoinbaseDataRaw).alignedBtcCandles;
|
|
11292
|
+
const ethData = universe === "tradfi" ? [] : (0, import_indicators4.alignSortedCandlesByTimestamp)(data, ethDataRaw).alignedBtcCandles;
|
|
10603
11293
|
const alignedExecution = shouldLoadExecutionCandles ? universe === "tradfi" ? {
|
|
10604
11294
|
alignedCoinCandles: executionDataRaw,
|
|
10605
11295
|
alignedBtcCandles: executionDataRaw
|
|
10606
|
-
} : (0,
|
|
11296
|
+
} : (0, import_indicators4.alignSortedCandlesByTimestamp)(executionDataRaw, executionBtcDataRaw) : { alignedCoinCandles: [], alignedBtcCandles: [] };
|
|
10607
11297
|
const { data: backtestExecutionData, btcData: backtestExecutionBtcData } = filterClosedAlignedCandles(
|
|
10608
11298
|
alignedExecution.alignedCoinCandles,
|
|
10609
11299
|
alignedExecution.alignedBtcCandles,
|