@tradejs/node 3.1.29-beta.289 → 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 +2504 -1883
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-KYGJBMKE.mjs → chunk-HRON2EAI.mjs} +432 -40
- 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 +837 -775
- package/dist/cli.mjs +4 -3
- package/dist/diagnostics.js +2532 -1911
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +4 -2
- package/dist/mcp.d.ts +4 -2
- package/dist/mcp.js +2614 -1971
- package/dist/mcp.mjs +27 -5
- 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 +2091 -1470
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2255 -1632
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2417 -1796
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2106 -1485
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/backtest.js
CHANGED
|
@@ -795,1650 +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
|
-
if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
|
|
1125
|
-
throw new EntrySubmissionValidationError(
|
|
1126
|
-
"ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
|
|
1127
|
-
);
|
|
1128
|
-
return { netRiskRatio, plannedLossValue };
|
|
1005
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
1006
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
1129
1007
|
};
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1139
|
-
if (
|
|
1140
|
-
return
|
|
1141
|
-
arrivalSnapshotTime: Date.now(),
|
|
1142
|
-
arrivalSource: "unavailable",
|
|
1143
|
-
bid: null,
|
|
1144
|
-
ask: null,
|
|
1145
|
-
arrivalMid: null,
|
|
1146
|
-
spreadBps: null
|
|
1147
|
-
};
|
|
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;
|
|
1148
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;
|
|
1149
1036
|
try {
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
+
]);
|
|
1165
1053
|
} catch (error) {
|
|
1166
|
-
import_logger.logger.
|
|
1167
|
-
"runtime
|
|
1168
|
-
symbol,
|
|
1054
|
+
import_logger.logger.error(
|
|
1055
|
+
"runtime trade open journal failed: %s %s",
|
|
1056
|
+
record.symbol,
|
|
1169
1057
|
error?.message || String(error)
|
|
1170
1058
|
);
|
|
1171
|
-
return {
|
|
1172
|
-
arrivalSnapshotTime: Date.now(),
|
|
1173
|
-
arrivalSource: "top_of_book_error",
|
|
1174
|
-
bid: null,
|
|
1175
|
-
ask: null,
|
|
1176
|
-
arrivalMid: null,
|
|
1177
|
-
spreadBps: null
|
|
1178
|
-
};
|
|
1179
1059
|
}
|
|
1060
|
+
return record;
|
|
1180
1061
|
};
|
|
1181
|
-
var
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
}
|
|
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;
|
|
1199
1079
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
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;
|
|
1202
1088
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
arrivalSnapshotTime: Date.now(),
|
|
1222
|
-
arrivalSource: "simulation",
|
|
1223
|
-
bid: executionPrice,
|
|
1224
|
-
ask: executionPrice,
|
|
1225
|
-
arrivalMid: executionPrice,
|
|
1226
|
-
spreadBps: 0
|
|
1227
|
-
} : await getOrderArrivalSnapshot({
|
|
1228
|
-
connector,
|
|
1229
|
-
symbol,
|
|
1230
|
-
requireTimestamp: true
|
|
1231
|
-
});
|
|
1232
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
1233
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
1234
|
-
throw new EntrySubmissionValidationError(
|
|
1235
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
1236
|
-
);
|
|
1237
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
1238
|
-
throw new EntrySubmissionValidationError(
|
|
1239
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
1240
|
-
);
|
|
1241
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
1242
|
-
validateEntryProtectionAtArrival({
|
|
1243
|
-
direction,
|
|
1244
|
-
signalPrice: price,
|
|
1245
|
-
bid: snapshot2.bid,
|
|
1246
|
-
ask: snapshot2.ask,
|
|
1247
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
1248
|
-
takeProfits,
|
|
1249
|
-
stopLossPrice
|
|
1250
|
-
});
|
|
1251
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
1252
|
-
const economics2 = validateEntryExecution({
|
|
1253
|
-
direction,
|
|
1254
|
-
price,
|
|
1255
|
-
qty,
|
|
1256
|
-
stopLossPrice,
|
|
1257
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
1258
|
-
limits
|
|
1259
|
-
});
|
|
1260
|
-
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()
|
|
1261
1107
|
};
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
}
|
|
1281
|
-
return trimmed.map(
|
|
1282
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
if (value && typeof value === "object") {
|
|
1286
|
-
return Object.fromEntries(
|
|
1287
|
-
Object.entries(value).map(([key, nested]) => [
|
|
1288
|
-
key,
|
|
1289
|
-
trimSeriesDeep(nested)
|
|
1290
|
-
])
|
|
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)
|
|
1291
1126
|
);
|
|
1292
1127
|
}
|
|
1293
|
-
return
|
|
1128
|
+
return next;
|
|
1294
1129
|
};
|
|
1295
|
-
var
|
|
1296
|
-
const
|
|
1297
|
-
if (
|
|
1298
|
-
return
|
|
1130
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
1131
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
1132
|
+
if (!userName) {
|
|
1133
|
+
return null;
|
|
1299
1134
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
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) {
|
|
1306
1141
|
return null;
|
|
1307
1142
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
};
|
|
1318
|
-
var buildMissingSpreadContext = () => ({
|
|
1319
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1320
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1321
|
-
available: false,
|
|
1322
|
-
value: null,
|
|
1323
|
-
zScore: null,
|
|
1324
|
-
bps: null,
|
|
1325
|
-
absBps: null,
|
|
1326
|
-
bias: null,
|
|
1327
|
-
severity: null
|
|
1328
|
-
});
|
|
1329
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
1330
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1331
|
-
const relative = toRecord(baseContext?.relative);
|
|
1332
|
-
const execution = toRecord(relative?.execution);
|
|
1333
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
1334
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
1335
|
-
if (spread == null) {
|
|
1336
|
-
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;
|
|
1337
1152
|
}
|
|
1338
|
-
|
|
1339
|
-
const bps = roundTo(value * 1e4, 2);
|
|
1340
|
-
const absBps = Math.abs(bps);
|
|
1341
|
-
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
1342
|
-
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
1343
|
-
return {
|
|
1344
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1345
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1346
|
-
available: true,
|
|
1347
|
-
value,
|
|
1348
|
-
zScore,
|
|
1349
|
-
bps,
|
|
1350
|
-
absBps,
|
|
1351
|
-
bias,
|
|
1352
|
-
severity
|
|
1353
|
-
};
|
|
1153
|
+
return existing;
|
|
1354
1154
|
};
|
|
1355
|
-
var
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
signedVolumeZScore: null
|
|
1371
|
-
};
|
|
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;
|
|
1372
1170
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
};
|
|
1384
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
1385
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1386
|
-
const participation = toRecord(baseContext?.participation);
|
|
1387
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
1388
|
-
if (!tradeFlow) {
|
|
1389
|
-
return {
|
|
1390
|
-
source: null,
|
|
1391
|
-
available: false,
|
|
1392
|
-
interval: null,
|
|
1393
|
-
stale: null,
|
|
1394
|
-
trades: null,
|
|
1395
|
-
buyPressurePct: null,
|
|
1396
|
-
netBaseDelta: null,
|
|
1397
|
-
netQuoteDelta: null
|
|
1398
|
-
};
|
|
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;
|
|
1399
1181
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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()
|
|
1409
1203
|
};
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
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
|
+
);
|
|
1430
1249
|
}
|
|
1431
|
-
return
|
|
1432
|
-
source: String(breadth.source ?? ""),
|
|
1433
|
-
available: true,
|
|
1434
|
-
universe: String(breadth.universe ?? ""),
|
|
1435
|
-
interval: String(breadth.interval ?? ""),
|
|
1436
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1437
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1438
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1439
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1440
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1441
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1442
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1443
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1444
|
-
};
|
|
1250
|
+
return next;
|
|
1445
1251
|
};
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
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);
|
|
1453
1277
|
return {
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
stale: null,
|
|
1459
|
-
symbolsCount: null,
|
|
1460
|
-
advanceDeclineRatio: null,
|
|
1461
|
-
pctAboveMa20: null,
|
|
1462
|
-
pctAboveMa50: null,
|
|
1463
|
-
equalWeightedReturn: null,
|
|
1464
|
-
volumeWeightedReturn: null,
|
|
1465
|
-
dispersion: null
|
|
1278
|
+
id: r.id,
|
|
1279
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
1280
|
+
filledQty,
|
|
1281
|
+
positionId: r.id
|
|
1466
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
|
|
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));
|
|
1467
1329
|
}
|
|
1468
|
-
return {
|
|
1469
|
-
source: String(breadth.source ?? ""),
|
|
1470
|
-
available: true,
|
|
1471
|
-
universe: String(breadth.universe ?? ""),
|
|
1472
|
-
interval: String(breadth.interval ?? ""),
|
|
1473
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1474
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1475
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1476
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1477
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1478
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1479
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1480
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1481
|
-
};
|
|
1482
1330
|
};
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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
|
|
1489
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();
|
|
1490
1464
|
};
|
|
1491
|
-
var
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
1495
|
-
if (!targetVsBtc) {
|
|
1496
|
-
return {
|
|
1497
|
-
source: null,
|
|
1498
|
-
available: false,
|
|
1499
|
-
ratioReturn1h: null,
|
|
1500
|
-
ratioReturn4h: null,
|
|
1501
|
-
ratioReturn24h: null,
|
|
1502
|
-
alphaVsBtc1h: null,
|
|
1503
|
-
alphaVsBtc4h: null,
|
|
1504
|
-
alphaVsBtc24h: null,
|
|
1505
|
-
betaToBtc20: null,
|
|
1506
|
-
correlationToBtc20: null,
|
|
1507
|
-
ratioTrend: null
|
|
1508
|
-
};
|
|
1465
|
+
var normalizeConfig = (rawConfig) => {
|
|
1466
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1467
|
+
return {};
|
|
1509
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;
|
|
1510
1477
|
return {
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
1517
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
1518
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
1519
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
1520
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
1521
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
1478
|
+
strategies: strategies2,
|
|
1479
|
+
indicators,
|
|
1480
|
+
connectors,
|
|
1481
|
+
...hooks ? { hooks } : {},
|
|
1482
|
+
...runtime ? { runtime } : {}
|
|
1522
1483
|
};
|
|
1523
1484
|
};
|
|
1524
|
-
var
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
if (!btcAltRegime) {
|
|
1529
|
-
return {
|
|
1530
|
-
source: null,
|
|
1531
|
-
available: false,
|
|
1532
|
-
universe: null,
|
|
1533
|
-
interval: null,
|
|
1534
|
-
stale: null,
|
|
1535
|
-
regime: null,
|
|
1536
|
-
btcReturn24h: null,
|
|
1537
|
-
altBasketReturn24h: null,
|
|
1538
|
-
btcVsAltReturn24h: null,
|
|
1539
|
-
btcTurnoverShare24h: null,
|
|
1540
|
-
btcTurnoverShareChange24h: null,
|
|
1541
|
-
altVolToBtcVol24h: null,
|
|
1542
|
-
altDispersion24h: null
|
|
1543
|
-
};
|
|
1485
|
+
var getNodeCreateRequire = () => {
|
|
1486
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
1487
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
1488
|
+
return builtinModule.createRequire;
|
|
1544
1489
|
}
|
|
1545
|
-
|
|
1546
|
-
source: String(btcAltRegime.source ?? ""),
|
|
1547
|
-
available: true,
|
|
1548
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
1549
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
1550
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1551
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1552
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1553
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1554
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1555
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1556
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1557
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
1558
|
-
),
|
|
1559
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1560
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1561
|
-
};
|
|
1490
|
+
throw new TypeError("module.createRequire is not available");
|
|
1562
1491
|
};
|
|
1563
|
-
var
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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);
|
|
1591
1744
|
}
|
|
1592
1745
|
return {
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
1596
|
-
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
1597
|
-
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
1598
|
-
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
1599
|
-
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
1600
|
-
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
1601
|
-
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
1602
|
-
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
1603
|
-
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
1604
|
-
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
1605
|
-
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
1606
|
-
btcDominanceChange24hPct: toFiniteNumber(
|
|
1607
|
-
cmcGlobal.btcDominanceChange24hPct
|
|
1608
|
-
),
|
|
1609
|
-
ethDominanceChange24hPct: toFiniteNumber(
|
|
1610
|
-
cmcGlobal.ethDominanceChange24hPct
|
|
1611
|
-
),
|
|
1612
|
-
altMarketCapChange24hPct: toFiniteNumber(
|
|
1613
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
1614
|
-
),
|
|
1615
|
-
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1616
|
-
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1617
|
-
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1618
|
-
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1619
|
-
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1746
|
+
projectRoot,
|
|
1747
|
+
state
|
|
1620
1748
|
};
|
|
1621
1749
|
};
|
|
1622
|
-
var
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
return {
|
|
1628
|
-
source: null,
|
|
1629
|
-
available: false,
|
|
1630
|
-
interval: null,
|
|
1631
|
-
asOfTs: null,
|
|
1632
|
-
stale: null,
|
|
1633
|
-
btcMarketCapUsd: null,
|
|
1634
|
-
ethMarketCapUsd: null,
|
|
1635
|
-
btcVolumeUsd: null,
|
|
1636
|
-
ethVolumeUsd: null,
|
|
1637
|
-
btcVolumeToMarketCap: null,
|
|
1638
|
-
ethVolumeToMarketCap: null,
|
|
1639
|
-
ethBtcMarketCapRatio: null,
|
|
1640
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
1641
|
-
ethVsBtcVolumeRatio: null,
|
|
1642
|
-
referenceLiquidityRegime: null
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
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);
|
|
1645
1755
|
return {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
1649
|
-
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
1650
|
-
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
1651
|
-
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
1652
|
-
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
1653
|
-
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
1654
|
-
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
1655
|
-
btcVolumeToMarketCap: toFiniteNumber(
|
|
1656
|
-
cmcReferenceAssets.btcVolumeToMarketCap
|
|
1657
|
-
),
|
|
1658
|
-
ethVolumeToMarketCap: toFiniteNumber(
|
|
1659
|
-
cmcReferenceAssets.ethVolumeToMarketCap
|
|
1660
|
-
),
|
|
1661
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1662
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1663
|
-
),
|
|
1664
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1665
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1666
|
-
),
|
|
1667
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1668
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1756
|
+
strategyModules: toUniqueModules(config.strategies),
|
|
1757
|
+
indicatorModules: toUniqueModules(config.indicators)
|
|
1669
1758
|
};
|
|
1670
1759
|
};
|
|
1671
|
-
var
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
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
|
+
);
|
|
1676
1902
|
return {
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
liquidityRegime: 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
|
|
1689
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;
|
|
1690
1922
|
}
|
|
1691
|
-
return {
|
|
1692
|
-
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
1693
|
-
available: true,
|
|
1694
|
-
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
1695
|
-
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
1696
|
-
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
1697
|
-
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
1698
|
-
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
1699
|
-
totalVolumeChange24hPct: toFiniteNumber(
|
|
1700
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
1701
|
-
),
|
|
1702
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
1703
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
1704
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
1705
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
1706
|
-
),
|
|
1707
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
1708
|
-
};
|
|
1709
1923
|
};
|
|
1710
|
-
var
|
|
1711
|
-
const
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
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
|
+
})();
|
|
1727
1998
|
}
|
|
1728
|
-
|
|
1729
|
-
source: String(cmcFearGreed.source ?? ""),
|
|
1730
|
-
available: true,
|
|
1731
|
-
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
1732
|
-
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
1733
|
-
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
1734
|
-
value: toFiniteNumber(cmcFearGreed.value),
|
|
1735
|
-
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
1736
|
-
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
1737
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
1738
|
-
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
1739
|
-
};
|
|
1999
|
+
await state.pluginsLoadPromise;
|
|
1740
2000
|
};
|
|
1741
|
-
var
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
if (!cmcIndexes) {
|
|
1746
|
-
return {
|
|
1747
|
-
source: null,
|
|
1748
|
-
available: false,
|
|
1749
|
-
interval: null,
|
|
1750
|
-
asOfTs: null,
|
|
1751
|
-
stale: null,
|
|
1752
|
-
cmc100Value: null,
|
|
1753
|
-
cmc100Change24hPct: null,
|
|
1754
|
-
cmc100TopConstituentSymbol: null,
|
|
1755
|
-
cmc100TopConstituentWeightPct: null,
|
|
1756
|
-
cmc20Value: null,
|
|
1757
|
-
cmc20Change24hPct: null,
|
|
1758
|
-
cmc20TopConstituentSymbol: null,
|
|
1759
|
-
cmc20TopConstituentWeightPct: null,
|
|
1760
|
-
cmc20ToCmc100Ratio: null,
|
|
1761
|
-
cmc20ToCmc100RatioChange24hPct: null,
|
|
1762
|
-
indexRegime: null
|
|
1763
|
-
};
|
|
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;
|
|
1764
2005
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
1769
|
-
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
1770
|
-
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
1771
|
-
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
1772
|
-
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
1773
|
-
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
1774
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
1775
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
1776
|
-
),
|
|
1777
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
1778
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1779
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1780
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1781
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1782
|
-
),
|
|
1783
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1784
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1785
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1786
|
-
),
|
|
1787
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1788
|
-
};
|
|
2006
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2007
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2008
|
+
return state.strategyCreators.get(name);
|
|
1789
2009
|
};
|
|
1790
|
-
var
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
const refs = toRecord(relative?.referenceTradeFlow);
|
|
1794
|
-
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
1795
|
-
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
1796
|
-
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
1797
|
-
if (!refs) {
|
|
1798
|
-
return {
|
|
1799
|
-
source: null,
|
|
1800
|
-
available: false,
|
|
1801
|
-
primaryReferenceSymbol: null,
|
|
1802
|
-
referenceSymbols: [],
|
|
1803
|
-
primaryTradeFlowBuyPressurePct: null,
|
|
1804
|
-
primaryTradeFlowStale: null
|
|
1805
|
-
};
|
|
2010
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
2011
|
+
if (!name) {
|
|
2012
|
+
return void 0;
|
|
1806
2013
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
available: true,
|
|
1810
|
-
primaryReferenceSymbol,
|
|
1811
|
-
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
1812
|
-
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
1813
|
-
primaryTradeFlow?.buyPressurePct
|
|
1814
|
-
),
|
|
1815
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1816
|
-
};
|
|
2014
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2015
|
+
return state.strategyManifestsMap.get(name);
|
|
1817
2016
|
};
|
|
1818
|
-
var
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1835
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1836
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
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
|
+
})
|
|
1837
2033
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
// src/strategy/manifests.ts
|
|
1841
|
-
var import_indicators = require("@tradejs/core/indicators");
|
|
2034
|
+
);
|
|
1842
2035
|
|
|
1843
|
-
// src/
|
|
1844
|
-
var
|
|
1845
|
-
var
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
var
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
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
|
+
}
|
|
1866
2089
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2090
|
+
return (await cached.value)?.get(symbol);
|
|
2091
|
+
};
|
|
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
|
|
2114
|
+
};
|
|
1870
2115
|
}
|
|
1871
|
-
return process.cwd();
|
|
1872
2116
|
};
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2117
|
+
|
|
2118
|
+
// src/strategyRuntime.ts
|
|
2119
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
2120
|
+
|
|
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;
|
|
2140
|
+
};
|
|
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
|
+
};
|
|
1876
2200
|
}
|
|
1877
|
-
const
|
|
1878
|
-
const
|
|
1879
|
-
const
|
|
1880
|
-
const
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
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);
|
|
1891
2277
|
};
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
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);
|
|
1897
2283
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
return;
|
|
2284
|
+
let record;
|
|
2285
|
+
try {
|
|
2286
|
+
record = await promise;
|
|
2287
|
+
} finally {
|
|
2288
|
+
pending.delete(pendingKey);
|
|
1904
2289
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
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
|
+
)
|
|
1912
2304
|
}
|
|
1913
|
-
}
|
|
1914
|
-
tsNodeRegistered = true;
|
|
1915
|
-
};
|
|
1916
|
-
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1917
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1918
|
-
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1919
|
-
return;
|
|
1920
|
-
}
|
|
1921
|
-
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1922
|
-
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1923
|
-
const register = tsconfigPathsModule.register;
|
|
1924
|
-
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1925
|
-
return;
|
|
1926
|
-
}
|
|
1927
|
-
const loadedConfig = loadConfig(projectRoot);
|
|
1928
|
-
if (loadedConfig.resultType !== "success") {
|
|
1929
|
-
return;
|
|
1930
|
-
}
|
|
1931
|
-
register({
|
|
1932
|
-
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1933
|
-
paths: loadedConfig.paths,
|
|
1934
|
-
addMatchAll: false
|
|
1935
|
-
});
|
|
1936
|
-
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
2305
|
+
};
|
|
1937
2306
|
};
|
|
1938
|
-
var
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
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
|
+
};
|
|
1954
2340
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
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
|
|
1958
2364
|
);
|
|
1959
|
-
|
|
1960
|
-
".ts",
|
|
1961
|
-
".tsx",
|
|
1962
|
-
".mts",
|
|
1963
|
-
".cts",
|
|
1964
|
-
".js",
|
|
1965
|
-
".jsx",
|
|
1966
|
-
".mjs",
|
|
1967
|
-
".cjs",
|
|
1968
|
-
".json"
|
|
1969
|
-
]) || "";
|
|
1970
|
-
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1971
|
-
const resolved = matcher(moduleName);
|
|
1972
|
-
return resolved || null;
|
|
1973
|
-
};
|
|
1974
|
-
var toImportSpecifier = (moduleName) => {
|
|
1975
|
-
if (moduleName.startsWith("file://")) {
|
|
1976
|
-
return moduleName;
|
|
1977
|
-
}
|
|
1978
|
-
if (import_path.default.isAbsolute(moduleName)) {
|
|
1979
|
-
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
1980
|
-
}
|
|
1981
|
-
return moduleName;
|
|
2365
|
+
return assessment;
|
|
1982
2366
|
};
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
var
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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";
|
|
1992
2386
|
}
|
|
1993
|
-
return true;
|
|
1994
2387
|
};
|
|
1995
|
-
var
|
|
1996
|
-
const
|
|
1997
|
-
const
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
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 };
|
|
2402
|
+
};
|
|
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)
|
|
2007
2427
|
);
|
|
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
|
+
};
|
|
2008
2442
|
};
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
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
|
+
};
|
|
2013
2461
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2462
|
+
try {
|
|
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
|
+
};
|
|
2478
|
+
} catch (error) {
|
|
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
|
+
};
|
|
2021
2492
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2493
|
+
};
|
|
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");
|
|
2029
2507
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
return requireFn(normalized);
|
|
2034
|
-
} catch (error) {
|
|
2035
|
-
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
2036
|
-
normalized,
|
|
2037
|
-
cwd
|
|
2038
|
-
);
|
|
2039
|
-
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
2040
|
-
return requireFn(resolvedByTsconfig);
|
|
2041
|
-
}
|
|
2042
|
-
throw error;
|
|
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");
|
|
2043
2511
|
}
|
|
2044
2512
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
/* webpackIgnore: true */
|
|
2048
|
-
toImportSpecifier(normalized)
|
|
2049
|
-
);
|
|
2050
|
-
} catch (error) {
|
|
2051
|
-
if (isTsModulePath(modulePath)) {
|
|
2052
|
-
await ensureTsNodeRegistered();
|
|
2053
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2054
|
-
return requireFn(modulePath);
|
|
2055
|
-
}
|
|
2056
|
-
throw error;
|
|
2513
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
2514
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
2057
2515
|
}
|
|
2058
2516
|
};
|
|
2059
|
-
var
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
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
|
+
);
|
|
2075
2581
|
}
|
|
2076
|
-
|
|
2077
|
-
}
|
|
2582
|
+
onValidated?.(snapshot2, economics2);
|
|
2583
|
+
};
|
|
2078
2584
|
};
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
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;
|
|
2089
2606
|
}
|
|
2607
|
+
return trimmed.map(
|
|
2608
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
2609
|
+
);
|
|
2090
2610
|
}
|
|
2091
|
-
if (
|
|
2092
|
-
return
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2611
|
+
if (value && typeof value === "object") {
|
|
2612
|
+
return Object.fromEntries(
|
|
2613
|
+
Object.entries(value).map(([key, nested]) => [
|
|
2614
|
+
key,
|
|
2615
|
+
trimSeriesDeep(nested)
|
|
2616
|
+
])
|
|
2617
|
+
);
|
|
2096
2618
|
}
|
|
2097
|
-
return
|
|
2619
|
+
return value;
|
|
2098
2620
|
};
|
|
2099
|
-
var
|
|
2100
|
-
const
|
|
2101
|
-
if (
|
|
2102
|
-
return
|
|
2103
|
-
}
|
|
2104
|
-
const configFilePath = findConfigFilePath(cwd);
|
|
2105
|
-
if (!configFilePath) {
|
|
2106
|
-
cachedByCwd.set(cwd, {});
|
|
2107
|
-
return {};
|
|
2108
|
-
}
|
|
2109
|
-
try {
|
|
2110
|
-
const moduleExports = await importConfigFile(configFilePath);
|
|
2111
|
-
const config = resolveExportedConfig(moduleExports);
|
|
2112
|
-
cachedByCwd.set(cwd, config);
|
|
2113
|
-
if (!announcedConfigFile.has(configFilePath)) {
|
|
2114
|
-
announcedConfigFile.add(configFilePath);
|
|
2115
|
-
import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
2116
|
-
}
|
|
2117
|
-
return config;
|
|
2118
|
-
} catch (error) {
|
|
2119
|
-
import_logger2.logger.log(
|
|
2120
|
-
"warn",
|
|
2121
|
-
"Failed to load TradeJS config from %s: %s",
|
|
2122
|
-
configFilePath,
|
|
2123
|
-
String(error)
|
|
2124
|
-
);
|
|
2125
|
-
cachedByCwd.set(cwd, {});
|
|
2126
|
-
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 });
|
|
2127
2625
|
}
|
|
2626
|
+
return trimSeriesDeep(value);
|
|
2128
2627
|
};
|
|
2129
2628
|
|
|
2130
|
-
// src/
|
|
2131
|
-
var
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
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
|
|
2143
2654
|
});
|
|
2144
|
-
var
|
|
2145
|
-
|
|
2146
|
-
const
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
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();
|
|
2151
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";
|
|
2152
2669
|
return {
|
|
2153
|
-
|
|
2154
|
-
|
|
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
|
|
2155
2679
|
};
|
|
2156
2680
|
};
|
|
2157
|
-
var
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
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
|
+
}
|
|
2162
2699
|
return {
|
|
2163
|
-
|
|
2164
|
-
|
|
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)
|
|
2165
2708
|
};
|
|
2166
2709
|
};
|
|
2167
|
-
var
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
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
|
+
};
|
|
2178
2725
|
}
|
|
2179
|
-
return
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
const indicatorEntries = extractModuleEntries(
|
|
2190
|
-
moduleExport,
|
|
2191
|
-
"indicatorEntries"
|
|
2192
|
-
);
|
|
2193
|
-
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
|
+
};
|
|
2194
2736
|
};
|
|
2195
|
-
var
|
|
2196
|
-
const
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
|
|
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
|
+
};
|
|
2216
2793
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
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
|
+
};
|
|
2219
2816
|
};
|
|
2220
|
-
var
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
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
|
+
};
|
|
2228
2834
|
};
|
|
2229
|
-
var
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
defaults: entry.defaults,
|
|
2240
|
-
createCore: entry.createCore,
|
|
2241
|
-
manifest: entry.manifest,
|
|
2242
|
-
detectorKey: entry.detectorKey,
|
|
2243
|
-
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
2244
|
-
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
2245
|
-
})
|
|
2246
|
-
);
|
|
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
|
+
};
|
|
2247
2845
|
};
|
|
2248
|
-
var
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
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
|
+
};
|
|
2254
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
|
+
};
|
|
2255
2895
|
};
|
|
2256
|
-
var
|
|
2257
|
-
|
|
2258
|
-
|
|
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
|
+
};
|
|
2259
2924
|
}
|
|
2260
|
-
return
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
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
|
+
};
|
|
2264
2954
|
};
|
|
2265
|
-
var
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
)
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
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) {
|
|
2960
|
+
return {
|
|
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
|
|
2976
|
+
};
|
|
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
|
+
};
|
|
3003
|
+
};
|
|
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) {
|
|
2309
3009
|
return {
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
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
|
|
2321
3022
|
};
|
|
2322
|
-
})();
|
|
2323
|
-
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
2324
|
-
try {
|
|
2325
|
-
return await resolution;
|
|
2326
|
-
} catch (error) {
|
|
2327
|
-
state.strategyModuleResolutions.delete(cacheKey);
|
|
2328
|
-
throw error;
|
|
2329
3023
|
}
|
|
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
|
+
};
|
|
2330
3042
|
};
|
|
2331
|
-
var
|
|
2332
|
-
const
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
2349
|
-
moduleName,
|
|
2350
|
-
projectRoot
|
|
2351
|
-
);
|
|
2352
|
-
const moduleExport = await importStrategyPluginModule(
|
|
2353
|
-
resolvedModuleName,
|
|
2354
|
-
projectRoot
|
|
2355
|
-
);
|
|
2356
|
-
if (strategySet.has(moduleName)) {
|
|
2357
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2358
|
-
if (!pluginDefinition) {
|
|
2359
|
-
issues.push(
|
|
2360
|
-
`${moduleName}: export { strategyEntries } is missing`
|
|
2361
|
-
);
|
|
2362
|
-
} else {
|
|
2363
|
-
const entryIssues = validateStrategyEntries(
|
|
2364
|
-
moduleName,
|
|
2365
|
-
pluginDefinition.strategyEntries,
|
|
2366
|
-
state
|
|
2367
|
-
);
|
|
2368
|
-
issues.push(...entryIssues);
|
|
2369
|
-
if (entryIssues.length === 0) {
|
|
2370
|
-
registerEntries(
|
|
2371
|
-
pluginDefinition.strategyEntries,
|
|
2372
|
-
moduleName,
|
|
2373
|
-
state
|
|
2374
|
-
);
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
if (indicatorSet.has(moduleName)) {
|
|
2379
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
2380
|
-
if (!indicatorPluginDefinition) {
|
|
2381
|
-
issues.push(
|
|
2382
|
-
`${moduleName}: export { indicatorEntries } is missing`
|
|
2383
|
-
);
|
|
2384
|
-
} else {
|
|
2385
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
2386
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
2387
|
-
moduleName,
|
|
2388
|
-
projectRoot
|
|
2389
|
-
);
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
2393
|
-
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2394
|
-
}
|
|
2395
|
-
} catch (error) {
|
|
2396
|
-
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2397
|
-
}
|
|
2398
|
-
}
|
|
2399
|
-
if (issues.length > 0) {
|
|
2400
|
-
throw new Error(
|
|
2401
|
-
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2402
|
-
);
|
|
2403
|
-
}
|
|
2404
|
-
})();
|
|
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
|
+
};
|
|
2405
3060
|
}
|
|
2406
|
-
|
|
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
|
+
};
|
|
2407
3073
|
};
|
|
2408
|
-
var
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
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
|
+
};
|
|
2412
3097
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
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
|
+
};
|
|
2416
3122
|
};
|
|
2417
|
-
var
|
|
2418
|
-
|
|
2419
|
-
|
|
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
|
+
};
|
|
2420
3139
|
}
|
|
2421
|
-
|
|
2422
|
-
|
|
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
|
+
};
|
|
2423
3150
|
};
|
|
2424
|
-
var
|
|
2425
|
-
{
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
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)
|
|
2440
3171
|
}
|
|
2441
|
-
);
|
|
3172
|
+
});
|
|
2442
3173
|
|
|
2443
3174
|
// src/strategy/policyProfiles.ts
|
|
2444
3175
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -2540,12 +3271,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
2540
3271
|
};
|
|
2541
3272
|
};
|
|
2542
3273
|
var defaultAiAdapter = {};
|
|
3274
|
+
var tickerProfileFeatureIds = new Set(
|
|
3275
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
3276
|
+
);
|
|
2543
3277
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
2544
3278
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
2545
3279
|
var buildAiPayloadByStrategy = (signal) => {
|
|
2546
3280
|
const basePayload = buildBaseAiPayload(signal);
|
|
2547
3281
|
const adapter = getSignalAiAdapter(signal);
|
|
2548
|
-
|
|
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
|
+
};
|
|
2549
3329
|
};
|
|
2550
3330
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
2551
3331
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -2958,353 +3738,105 @@ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
|
2958
3738
|
{ role: "system", content: systemPrompt },
|
|
2959
3739
|
{
|
|
2960
3740
|
role: "system",
|
|
2961
|
-
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
2962
|
-
},
|
|
2963
|
-
{ role: "user", content: humanPrompt }
|
|
2964
|
-
]
|
|
2965
|
-
});
|
|
2966
|
-
const parsed = parseAIResponse(response.content);
|
|
2967
|
-
const normalized = normalizeAnalysis(parsed);
|
|
2968
|
-
if (!options.signal) {
|
|
2969
|
-
return normalized;
|
|
2970
|
-
}
|
|
2971
|
-
return postProcessAiAnalysisByStrategy(
|
|
2972
|
-
options.signal,
|
|
2973
|
-
normalized,
|
|
2974
|
-
options.payload
|
|
2975
|
-
);
|
|
2976
|
-
};
|
|
2977
|
-
var runAiPromptLocal = async (signal, options = {}) => {
|
|
2978
|
-
const payload = options.payload ?? buildAiPayload(signal);
|
|
2979
|
-
const gateContext = getDeterministicAiGateContext(payload);
|
|
2980
|
-
const signalDirection = getSignalDirection(signal);
|
|
2981
|
-
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
2982
|
-
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
2983
|
-
return postProcessLocalAiAnalysisByStrategy(
|
|
2984
|
-
signal,
|
|
2985
|
-
{
|
|
2986
|
-
direction: approvalAllowedNow ? signalDirection : null,
|
|
2987
|
-
quality: deterministicQuality,
|
|
2988
|
-
needRetest: !approvalAllowedNow,
|
|
2989
|
-
retestPrice: null,
|
|
2990
|
-
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
2991
|
-
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
2992
|
-
},
|
|
2993
|
-
payload
|
|
2994
|
-
);
|
|
2995
|
-
};
|
|
2996
|
-
var askAI = async (signal, options = {}) => {
|
|
2997
|
-
const { symbol } = signal;
|
|
2998
|
-
const payload = buildAiPayload(signal);
|
|
2999
|
-
const content = await runAiPrompt(
|
|
3000
|
-
{
|
|
3001
|
-
systemPrompt: buildAiSystemPrompt(signal),
|
|
3002
|
-
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
3003
|
-
},
|
|
3004
|
-
{
|
|
3005
|
-
...options,
|
|
3006
|
-
signal,
|
|
3007
|
-
payload
|
|
3008
|
-
}
|
|
3009
|
-
);
|
|
3010
|
-
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3011
|
-
return content;
|
|
3012
|
-
};
|
|
3013
|
-
|
|
3014
|
-
// src/strategyAdapters/ml.ts
|
|
3015
|
-
var defaultMlAdapter = {
|
|
3016
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3017
|
-
};
|
|
3018
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3019
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3020
|
-
getStrategyManifest(strategy),
|
|
3021
|
-
profileId
|
|
3022
|
-
);
|
|
3023
|
-
if (!strategyAdapter) return defaultMlAdapter;
|
|
3024
|
-
return {
|
|
3025
|
-
...defaultMlAdapter,
|
|
3026
|
-
...strategyAdapter
|
|
3027
|
-
};
|
|
3028
|
-
};
|
|
3029
|
-
|
|
3030
|
-
// src/mlPayload.ts
|
|
3031
|
-
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3032
|
-
return getStrategyMlAdapter(
|
|
3033
|
-
strategyName,
|
|
3034
|
-
profileId
|
|
3035
|
-
).normalizeStrategyConfig?.(strategyConfig);
|
|
3036
|
-
};
|
|
3037
|
-
var buildMlPayload = (payload) => {
|
|
3038
|
-
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3039
|
-
const profileId = payload.signal?.policyProfileId;
|
|
3040
|
-
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3041
|
-
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3042
|
-
const nextSignal = {
|
|
3043
|
-
...normalizedSignal,
|
|
3044
|
-
indicators: {
|
|
3045
|
-
...normalizedSignal?.indicators ?? {}
|
|
3046
|
-
}
|
|
3047
|
-
};
|
|
3048
|
-
const nextContext = payload.context ? {
|
|
3049
|
-
...payload.context,
|
|
3050
|
-
strategyConfig: normalizeStrategyConfig(
|
|
3051
|
-
payload.context.strategyConfig,
|
|
3052
|
-
strategyName,
|
|
3053
|
-
profileId
|
|
3054
|
-
)
|
|
3055
|
-
} : void 0;
|
|
3056
|
-
return {
|
|
3057
|
-
signal: nextSignal,
|
|
3058
|
-
context: nextContext
|
|
3059
|
-
};
|
|
3060
|
-
};
|
|
3061
|
-
|
|
3062
|
-
// src/runtimeJournal.ts
|
|
3063
|
-
var import_node_crypto = require("crypto");
|
|
3064
|
-
var import_constants = require("@tradejs/core/constants");
|
|
3065
|
-
var import_time = require("@tradejs/core/time");
|
|
3066
|
-
var import_trade = require("@tradejs/core/trade");
|
|
3067
|
-
var import_logger3 = require("@tradejs/infra/logger");
|
|
3068
|
-
var import_redis2 = require("@tradejs/infra/redis");
|
|
3069
|
-
var now = () => Date.now();
|
|
3070
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
3071
|
-
var calculateClosedPnl = ({
|
|
3072
|
-
direction,
|
|
3073
|
-
entryPrice,
|
|
3074
|
-
exitPrice,
|
|
3075
|
-
qty
|
|
3076
|
-
}) => {
|
|
3077
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
3078
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
3079
|
-
};
|
|
3080
|
-
var createRuntimeOrderId = (strategy) => {
|
|
3081
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
3082
|
-
if (prefix === "tjs-") {
|
|
3083
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
3084
|
-
}
|
|
3085
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
3086
|
-
};
|
|
3087
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
3088
|
-
const { userName } = params;
|
|
3089
|
-
if (!userName) {
|
|
3090
|
-
return null;
|
|
3091
|
-
}
|
|
3092
|
-
const record = {
|
|
3093
|
-
...params,
|
|
3094
|
-
entryCount: 1,
|
|
3095
|
-
lastEntryPrice: params.entryPrice,
|
|
3096
|
-
lastEntryQty: params.qty,
|
|
3097
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
3098
|
-
status: "active",
|
|
3099
|
-
currentPrice: params.entryPrice,
|
|
3100
|
-
currentPnl: 0,
|
|
3101
|
-
closedPnl: null,
|
|
3102
|
-
exitPrice: null,
|
|
3103
|
-
exitTimestamp: null,
|
|
3104
|
-
lastSyncedAt: now()
|
|
3105
|
-
};
|
|
3106
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
3107
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
3108
|
-
try {
|
|
3109
|
-
await Promise.all([
|
|
3110
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
3111
|
-
expire: 0
|
|
3112
|
-
}),
|
|
3113
|
-
(0, import_redis2.setHashJsonField)(
|
|
3114
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3115
|
-
record.orderId,
|
|
3116
|
-
record,
|
|
3117
|
-
{ expire: 0 }
|
|
3118
|
-
),
|
|
3119
|
-
(0, import_redis2.setData)(
|
|
3120
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
3121
|
-
{ orderId: record.orderId },
|
|
3122
|
-
{ expire: 0 }
|
|
3123
|
-
)
|
|
3124
|
-
]);
|
|
3125
|
-
} catch (error) {
|
|
3126
|
-
import_logger3.logger.error(
|
|
3127
|
-
"runtime trade open journal failed: %s %s",
|
|
3128
|
-
record.symbol,
|
|
3129
|
-
error?.message || String(error)
|
|
3130
|
-
);
|
|
3131
|
-
}
|
|
3132
|
-
return record;
|
|
3133
|
-
};
|
|
3134
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
3135
|
-
const {
|
|
3136
|
-
userName,
|
|
3137
|
-
strategy,
|
|
3138
|
-
symbol,
|
|
3139
|
-
direction,
|
|
3140
|
-
resultingQty,
|
|
3141
|
-
resultingEntryPrice,
|
|
3142
|
-
addedQty,
|
|
3143
|
-
addedEntryPrice,
|
|
3144
|
-
entryTimestamp,
|
|
3145
|
-
fee,
|
|
3146
|
-
accountId,
|
|
3147
|
-
deploymentId
|
|
3148
|
-
} = params;
|
|
3149
|
-
if (!userName) {
|
|
3150
|
-
return null;
|
|
3151
|
-
}
|
|
3152
|
-
const existing = await getActiveRuntimeTrade({
|
|
3153
|
-
userName,
|
|
3154
|
-
symbol,
|
|
3155
|
-
accountId,
|
|
3156
|
-
deploymentId
|
|
3741
|
+
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
3742
|
+
},
|
|
3743
|
+
{ role: "user", content: humanPrompt }
|
|
3744
|
+
]
|
|
3157
3745
|
});
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
3163
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3164
|
-
const next = {
|
|
3165
|
-
...existing,
|
|
3166
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3167
|
-
qty: resultingQty,
|
|
3168
|
-
entryPrice: resultingEntryPrice,
|
|
3169
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3170
|
-
lastEntryPrice: addedEntryPrice,
|
|
3171
|
-
lastEntryQty: addedQty,
|
|
3172
|
-
lastEntryTimestamp: entryTimestamp,
|
|
3173
|
-
currentPrice: resultingEntryPrice,
|
|
3174
|
-
currentPnl: 0,
|
|
3175
|
-
fee: openFee,
|
|
3176
|
-
openFee,
|
|
3177
|
-
totalFee,
|
|
3178
|
-
lastSyncedAt: now()
|
|
3179
|
-
};
|
|
3180
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3181
|
-
try {
|
|
3182
|
-
await Promise.all([
|
|
3183
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3184
|
-
expire: 0
|
|
3185
|
-
}),
|
|
3186
|
-
(0, import_redis2.setHashJsonField)(
|
|
3187
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3188
|
-
existing.orderId,
|
|
3189
|
-
next,
|
|
3190
|
-
{ expire: 0 }
|
|
3191
|
-
)
|
|
3192
|
-
]);
|
|
3193
|
-
} catch (error) {
|
|
3194
|
-
import_logger3.logger.error(
|
|
3195
|
-
"runtime trade increase journal failed: %s %s",
|
|
3196
|
-
symbol,
|
|
3197
|
-
error?.message || String(error)
|
|
3198
|
-
);
|
|
3746
|
+
const parsed = parseAIResponse(response.content);
|
|
3747
|
+
const normalized = normalizeAnalysis(parsed);
|
|
3748
|
+
if (!options.signal) {
|
|
3749
|
+
return normalized;
|
|
3199
3750
|
}
|
|
3200
|
-
return
|
|
3751
|
+
return postProcessAiAnalysisByStrategy(
|
|
3752
|
+
options.signal,
|
|
3753
|
+
normalized,
|
|
3754
|
+
options.payload
|
|
3755
|
+
);
|
|
3201
3756
|
};
|
|
3202
|
-
var
|
|
3203
|
-
const
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
const
|
|
3208
|
-
|
|
3209
|
-
|
|
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
|
|
3210
3774
|
);
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
const
|
|
3216
|
-
|
|
3217
|
-
|
|
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
|
+
}
|
|
3218
3789
|
);
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3222
|
-
);
|
|
3223
|
-
return null;
|
|
3224
|
-
}
|
|
3225
|
-
return existing;
|
|
3790
|
+
await (0, import_redis3.setData)(import_redis3.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3791
|
+
return content;
|
|
3226
3792
|
};
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
const
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
}
|
|
3262
|
-
const
|
|
3263
|
-
...
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
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
|
|
3272
3839
|
};
|
|
3273
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3274
|
-
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3275
|
-
try {
|
|
3276
|
-
await Promise.all([
|
|
3277
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
3278
|
-
expire: import_constants.TTL_1M
|
|
3279
|
-
}),
|
|
3280
|
-
(0, import_redis2.setHashJsonField)(
|
|
3281
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3282
|
-
orderId,
|
|
3283
|
-
next,
|
|
3284
|
-
{ expire: import_constants.TTL_1M }
|
|
3285
|
-
),
|
|
3286
|
-
(0, import_redis2.setHashJsonField)(
|
|
3287
|
-
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3288
|
-
orderId,
|
|
3289
|
-
next,
|
|
3290
|
-
{ expire: import_constants.TTL_1M }
|
|
3291
|
-
),
|
|
3292
|
-
(0, import_redis2.delKey)(
|
|
3293
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3294
|
-
userName,
|
|
3295
|
-
symbol,
|
|
3296
|
-
deploymentId ?? accountId
|
|
3297
|
-
)
|
|
3298
|
-
)
|
|
3299
|
-
]);
|
|
3300
|
-
} catch (error) {
|
|
3301
|
-
import_logger3.logger.error(
|
|
3302
|
-
"runtime trade close journal failed: %s %s",
|
|
3303
|
-
symbol,
|
|
3304
|
-
error?.message || String(error)
|
|
3305
|
-
);
|
|
3306
|
-
}
|
|
3307
|
-
return next;
|
|
3308
3840
|
};
|
|
3309
3841
|
|
|
3310
3842
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -3316,7 +3848,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3316
3848
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3317
3849
|
|
|
3318
3850
|
// src/binanceBreadthUniverses.ts
|
|
3319
|
-
var
|
|
3851
|
+
var import_node_crypto3 = require("crypto");
|
|
3320
3852
|
|
|
3321
3853
|
// src/config/binanceBreadthUniverses.json
|
|
3322
3854
|
var binanceBreadthUniverses_default = {
|
|
@@ -3555,7 +4087,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3555
4087
|
"top50",
|
|
3556
4088
|
"top100"
|
|
3557
4089
|
];
|
|
3558
|
-
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);
|
|
3559
4091
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3560
4092
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3561
4093
|
rankedSymbols,
|
|
@@ -4579,7 +5111,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4579
5111
|
};
|
|
4580
5112
|
|
|
4581
5113
|
// src/strategyHelpers/derivativesContext.ts
|
|
4582
|
-
var
|
|
5114
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4583
5115
|
var import_data2 = require("@tradejs/core/data");
|
|
4584
5116
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4585
5117
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4613,7 +5145,7 @@ var parseLookbackMs = () => {
|
|
|
4613
5145
|
};
|
|
4614
5146
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4615
5147
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4616
|
-
"1h": (0,
|
|
5148
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4617
5149
|
rows15m: rowsByInterval["15m"],
|
|
4618
5150
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4619
5151
|
})
|
|
@@ -4741,7 +5273,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4741
5273
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4742
5274
|
const lookbackMs = parseLookbackMs();
|
|
4743
5275
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4744
|
-
const derivativesEndMs = (0,
|
|
5276
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4745
5277
|
decisionTimeMs,
|
|
4746
5278
|
"15m"
|
|
4747
5279
|
);
|
|
@@ -4756,7 +5288,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4756
5288
|
});
|
|
4757
5289
|
return [
|
|
4758
5290
|
symbol,
|
|
4759
|
-
(0,
|
|
5291
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
4760
5292
|
symbol,
|
|
4761
5293
|
direction: signal.direction,
|
|
4762
5294
|
timestamp: derivativesEndMs,
|
|
@@ -4783,7 +5315,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4783
5315
|
lookbackMs,
|
|
4784
5316
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
4785
5317
|
});
|
|
4786
|
-
const context = (0,
|
|
5318
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
4787
5319
|
symbol: targetSymbol,
|
|
4788
5320
|
direction: signal.direction,
|
|
4789
5321
|
timestamp: derivativesEndMs,
|
|
@@ -4831,7 +5363,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
4831
5363
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
4832
5364
|
|
|
4833
5365
|
// src/hyperliquidWhaleUniverse.ts
|
|
4834
|
-
var
|
|
5366
|
+
var import_node_crypto4 = require("crypto");
|
|
4835
5367
|
|
|
4836
5368
|
// src/config/hyperliquidPerpUniverse.json
|
|
4837
5369
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5003,7 +5535,7 @@ var hyperliquidWhales_default = {
|
|
|
5003
5535
|
};
|
|
5004
5536
|
|
|
5005
5537
|
// src/hyperliquidWhaleUniverse.ts
|
|
5006
|
-
var fingerprint2 = (value) => (0,
|
|
5538
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5007
5539
|
var validateUnique = (values, label) => {
|
|
5008
5540
|
if (new Set(values).size !== values.length) {
|
|
5009
5541
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5490,6 +6022,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5490
6022
|
})
|
|
5491
6023
|
});
|
|
5492
6024
|
}
|
|
6025
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5493
6026
|
const results = [];
|
|
5494
6027
|
for (const stage of stages) {
|
|
5495
6028
|
if (abortSignal?.aborted) break;
|
|
@@ -5537,12 +6070,12 @@ var findReplayAiAnalysis = ({
|
|
|
5537
6070
|
direction,
|
|
5538
6071
|
ai
|
|
5539
6072
|
}) => {
|
|
5540
|
-
const
|
|
5541
|
-
if (!Array.isArray(
|
|
6073
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6074
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5542
6075
|
return void 0;
|
|
5543
6076
|
}
|
|
5544
6077
|
let best = null;
|
|
5545
|
-
for (const snapshot2 of
|
|
6078
|
+
for (const snapshot2 of snapshots2) {
|
|
5546
6079
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5547
6080
|
continue;
|
|
5548
6081
|
}
|
|
@@ -5702,7 +6235,9 @@ var executeEntryOrder = async ({
|
|
|
5702
6235
|
beforePlaceOrder,
|
|
5703
6236
|
recordRuntimeTrade = true,
|
|
5704
6237
|
leverage,
|
|
5705
|
-
executionLimits
|
|
6238
|
+
executionLimits,
|
|
6239
|
+
markPortfolioSubmitted,
|
|
6240
|
+
rejectPortfolioSubmission
|
|
5706
6241
|
}) => {
|
|
5707
6242
|
await beforePlaceOrder?.();
|
|
5708
6243
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -5735,12 +6270,12 @@ var executeEntryOrder = async ({
|
|
|
5735
6270
|
if (executionLimits?.maxLossValue != null) {
|
|
5736
6271
|
signal.riskDecision = {
|
|
5737
6272
|
stage: "order_plan",
|
|
5738
|
-
status: "approved",
|
|
5739
|
-
reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
6273
|
+
status: economics2.riskExceeded ? "warning" : "approved",
|
|
6274
|
+
reason: economics2.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
5740
6275
|
plannedLossValue: economics2.plannedLossValue,
|
|
5741
6276
|
maxLossValue: executionLimits.maxLossValue,
|
|
5742
6277
|
limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
|
|
5743
|
-
enforced:
|
|
6278
|
+
enforced: false
|
|
5744
6279
|
};
|
|
5745
6280
|
}
|
|
5746
6281
|
}
|
|
@@ -5775,7 +6310,13 @@ var executeEntryOrder = async ({
|
|
|
5775
6310
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
5776
6311
|
orderId,
|
|
5777
6312
|
signal,
|
|
5778
|
-
...
|
|
6313
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6314
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6315
|
+
beforeSubmit: async (context) => {
|
|
6316
|
+
await beforeSubmit?.(context);
|
|
6317
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
6318
|
+
}
|
|
6319
|
+
} : {}
|
|
5779
6320
|
});
|
|
5780
6321
|
const orderAckTime = Date.now();
|
|
5781
6322
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -6766,7 +7307,7 @@ var handleProtectDecision = async ({
|
|
|
6766
7307
|
}
|
|
6767
7308
|
return decision.code;
|
|
6768
7309
|
};
|
|
6769
|
-
var
|
|
7310
|
+
var executeAdmittedEntryDecision = async ({
|
|
6770
7311
|
connector,
|
|
6771
7312
|
symbol,
|
|
6772
7313
|
decision,
|
|
@@ -6780,7 +7321,9 @@ var executeEntryDecision = async ({
|
|
|
6780
7321
|
ai,
|
|
6781
7322
|
recordRuntimeJournal,
|
|
6782
7323
|
invokeStageHooks,
|
|
6783
|
-
notifyRuntimeError
|
|
7324
|
+
notifyRuntimeError,
|
|
7325
|
+
markPortfolioSubmitted,
|
|
7326
|
+
rejectPortfolioSubmission
|
|
6784
7327
|
}) => {
|
|
6785
7328
|
const signal = decision.signal;
|
|
6786
7329
|
const beforePlaceOrder = async () => {
|
|
@@ -6827,6 +7370,8 @@ var executeEntryDecision = async ({
|
|
|
6827
7370
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
6828
7371
|
signal,
|
|
6829
7372
|
beforePlaceOrder,
|
|
7373
|
+
markPortfolioSubmitted,
|
|
7374
|
+
rejectPortfolioSubmission,
|
|
6830
7375
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
6831
7376
|
executionLimits: resolveEntryExecutionLimits(
|
|
6832
7377
|
hookCtx.strategyConfig,
|
|
@@ -6967,6 +7512,37 @@ var executeEntryDecision = async ({
|
|
|
6967
7512
|
}
|
|
6968
7513
|
return signal ?? decision.code;
|
|
6969
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
|
+
}
|
|
6970
7546
|
|
|
6971
7547
|
// src/strategyRuntime.ts
|
|
6972
7548
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -6994,6 +7570,7 @@ var createStrategyRuntime = ({
|
|
|
6994
7570
|
};
|
|
6995
7571
|
const creator = async ({
|
|
6996
7572
|
userName,
|
|
7573
|
+
connectorName,
|
|
6997
7574
|
config: baseConfig,
|
|
6998
7575
|
symbol,
|
|
6999
7576
|
universe: requestedUniverse,
|
|
@@ -7032,6 +7609,17 @@ var createStrategyRuntime = ({
|
|
|
7032
7609
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7033
7610
|
const projectHooks = projectConfig.hooks;
|
|
7034
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;
|
|
7035
7623
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7036
7624
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7037
7625
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7069,6 +7657,9 @@ var createStrategyRuntime = ({
|
|
|
7069
7657
|
});
|
|
7070
7658
|
const strategyPolicyProfile = getPolicyProfile();
|
|
7071
7659
|
const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
|
|
7660
|
+
const strategyConfig = { ...config };
|
|
7661
|
+
delete strategyConfig.RECORD_RUNTIME_TRADES;
|
|
7662
|
+
delete strategyConfig.SIMULATE_ORDERS;
|
|
7072
7663
|
const hookBase = {
|
|
7073
7664
|
connector,
|
|
7074
7665
|
strategyName,
|
|
@@ -7079,7 +7670,7 @@ var createStrategyRuntime = ({
|
|
|
7079
7670
|
accountId,
|
|
7080
7671
|
deploymentId,
|
|
7081
7672
|
policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
|
|
7082
|
-
strategyConfig
|
|
7673
|
+
strategyConfig,
|
|
7083
7674
|
env,
|
|
7084
7675
|
isConfigFromBacktest
|
|
7085
7676
|
};
|
|
@@ -7147,7 +7738,7 @@ var createStrategyRuntime = ({
|
|
|
7147
7738
|
);
|
|
7148
7739
|
}
|
|
7149
7740
|
};
|
|
7150
|
-
const invokeHook = async (stage, hook, params, errorContext = {}) => {
|
|
7741
|
+
const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
|
|
7151
7742
|
if (!hook) {
|
|
7152
7743
|
return void 0;
|
|
7153
7744
|
}
|
|
@@ -7167,17 +7758,18 @@ var createStrategyRuntime = ({
|
|
|
7167
7758
|
entry: errorContext.entry,
|
|
7168
7759
|
market: errorContext.market
|
|
7169
7760
|
});
|
|
7170
|
-
return
|
|
7761
|
+
return failureResult;
|
|
7171
7762
|
}
|
|
7172
7763
|
};
|
|
7173
|
-
const invokeProjectHooks = async (stage, params, errorContext = {}) => {
|
|
7764
|
+
const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
|
|
7174
7765
|
const results = [];
|
|
7175
7766
|
for (const hook of getProjectHookList(stage)) {
|
|
7176
7767
|
const result = await invokeHook(
|
|
7177
7768
|
stage,
|
|
7178
7769
|
hook,
|
|
7179
7770
|
params,
|
|
7180
|
-
errorContext
|
|
7771
|
+
errorContext,
|
|
7772
|
+
failureResult
|
|
7181
7773
|
);
|
|
7182
7774
|
if (result !== void 0) {
|
|
7183
7775
|
results.push(result);
|
|
@@ -7192,10 +7784,12 @@ var createStrategyRuntime = ({
|
|
|
7192
7784
|
return invokeHook(stage, hook, params, errorContext);
|
|
7193
7785
|
};
|
|
7194
7786
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7787
|
+
const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
|
|
7195
7788
|
const projectResults = await invokeProjectHooks(
|
|
7196
7789
|
stage,
|
|
7197
7790
|
params,
|
|
7198
|
-
errorContext
|
|
7791
|
+
errorContext,
|
|
7792
|
+
failureResult
|
|
7199
7793
|
);
|
|
7200
7794
|
const projectBlock = projectResults.find(
|
|
7201
7795
|
(result) => result?.allow === false
|
|
@@ -7207,7 +7801,8 @@ var createStrategyRuntime = ({
|
|
|
7207
7801
|
stage,
|
|
7208
7802
|
hook,
|
|
7209
7803
|
params,
|
|
7210
|
-
errorContext
|
|
7804
|
+
errorContext,
|
|
7805
|
+
failureResult
|
|
7211
7806
|
);
|
|
7212
7807
|
};
|
|
7213
7808
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
@@ -7323,6 +7918,20 @@ var createStrategyRuntime = ({
|
|
|
7323
7918
|
interval: decisionInterval
|
|
7324
7919
|
}) => {
|
|
7325
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
|
+
}
|
|
7326
7935
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7327
7936
|
return baseContext;
|
|
7328
7937
|
}
|
|
@@ -7353,11 +7962,8 @@ var createStrategyRuntime = ({
|
|
|
7353
7962
|
return enrichedBaseContext;
|
|
7354
7963
|
}
|
|
7355
7964
|
});
|
|
7356
|
-
const coreConfig = { ...config };
|
|
7357
|
-
delete coreConfig.RECORD_RUNTIME_TRADES;
|
|
7358
|
-
delete coreConfig.SIMULATE_ORDERS;
|
|
7359
7965
|
const core = await createCore({
|
|
7360
|
-
config:
|
|
7966
|
+
config: strategyConfig,
|
|
7361
7967
|
data,
|
|
7362
7968
|
strategyApi,
|
|
7363
7969
|
indicatorsState
|
|
@@ -7493,6 +8099,8 @@ var createStrategyRuntime = ({
|
|
|
7493
8099
|
runtime: pending2.runtime
|
|
7494
8100
|
});
|
|
7495
8101
|
return executeEntryDecision({
|
|
8102
|
+
portfolio,
|
|
8103
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7496
8104
|
connector,
|
|
7497
8105
|
symbol,
|
|
7498
8106
|
decision: pending2.decision,
|
|
@@ -7510,6 +8118,10 @@ var createStrategyRuntime = ({
|
|
|
7510
8118
|
});
|
|
7511
8119
|
};
|
|
7512
8120
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8121
|
+
if (portfolioContext)
|
|
8122
|
+
await portfolioContext(
|
|
8123
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8124
|
+
);
|
|
7513
8125
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7514
8126
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7515
8127
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7680,6 +8292,13 @@ var createStrategyRuntime = ({
|
|
|
7680
8292
|
});
|
|
7681
8293
|
const signal = decision.signal;
|
|
7682
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
|
+
};
|
|
7683
8302
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7684
8303
|
if (universe) signal.universe = universe;
|
|
7685
8304
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -7919,6 +8538,8 @@ var createStrategyRuntime = ({
|
|
|
7919
8538
|
});
|
|
7920
8539
|
}
|
|
7921
8540
|
return executeEntryDecision({
|
|
8541
|
+
portfolio,
|
|
8542
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7922
8543
|
connector,
|
|
7923
8544
|
symbol,
|
|
7924
8545
|
decision,
|
|
@@ -8701,7 +9322,7 @@ var resolveExecutionCosts = async (params) => {
|
|
|
8701
9322
|
};
|
|
8702
9323
|
|
|
8703
9324
|
// src/testConnector.ts
|
|
8704
|
-
var
|
|
9325
|
+
var import_node_crypto5 = require("crypto");
|
|
8705
9326
|
var import_constants8 = require("@tradejs/core/constants");
|
|
8706
9327
|
var import_backtest2 = require("@tradejs/core/backtest");
|
|
8707
9328
|
var import_trade2 = require("@tradejs/core/trade");
|
|
@@ -9174,7 +9795,7 @@ var createTestConnector = (connector, context) => {
|
|
|
9174
9795
|
},
|
|
9175
9796
|
kline: async (options) => connector.kline(options),
|
|
9176
9797
|
getResult: async () => {
|
|
9177
|
-
const orderLogId = (0,
|
|
9798
|
+
const orderLogId = (0, import_node_crypto5.randomUUID)().slice(-12);
|
|
9178
9799
|
const fullStat = fastMode ? (0, import_backtest2.calculateStatsFull)(positionLog) : null;
|
|
9179
9800
|
return {
|
|
9180
9801
|
stat: fullStat ? {
|
|
@@ -10590,19 +11211,19 @@ var prepareTestingData = async (params) => {
|
|
|
10590
11211
|
if (shouldLoadExecutionCandles && !cachedExecutionBtcData) {
|
|
10591
11212
|
state.btcKlineCache.set(executionBtcCacheKey, executionBtcDataRaw);
|
|
10592
11213
|
}
|
|
10593
|
-
const aligned = universe === "tradfi" ? { alignedCoinCandles: dataRaw, alignedBtcCandles: dataRaw } : (0,
|
|
11214
|
+
const aligned = universe === "tradfi" ? { alignedCoinCandles: dataRaw, alignedBtcCandles: dataRaw } : (0, import_indicators4.alignSortedCandlesByTimestamp)(dataRaw, btcDataRaw);
|
|
10594
11215
|
const { data, btcData } = filterClosedAlignedCandles(
|
|
10595
11216
|
aligned.alignedCoinCandles,
|
|
10596
11217
|
aligned.alignedBtcCandles,
|
|
10597
11218
|
interval
|
|
10598
11219
|
);
|
|
10599
|
-
const btcBinanceData = universe === "tradfi" ? [] : (0,
|
|
10600
|
-
const btcCoinbaseData = universe === "tradfi" ? [] : (0,
|
|
10601
|
-
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;
|
|
10602
11223
|
const alignedExecution = shouldLoadExecutionCandles ? universe === "tradfi" ? {
|
|
10603
11224
|
alignedCoinCandles: executionDataRaw,
|
|
10604
11225
|
alignedBtcCandles: executionDataRaw
|
|
10605
|
-
} : (0,
|
|
11226
|
+
} : (0, import_indicators4.alignSortedCandlesByTimestamp)(executionDataRaw, executionBtcDataRaw) : { alignedCoinCandles: [], alignedBtcCandles: [] };
|
|
10606
11227
|
const { data: backtestExecutionData, btcData: backtestExecutionBtcData } = filterClosedAlignedCandles(
|
|
10607
11228
|
alignedExecution.alignedCoinCandles,
|
|
10608
11229
|
alignedExecution.alignedBtcCandles,
|