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