@tradejs/node 3.1.29-beta.290 → 3.1.29-beta.291

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -808,1671 +808,2405 @@ __export(diagnostics_exports, {
808
808
  module.exports = __toCommonJS(diagnostics_exports);
809
809
 
810
810
  // src/diagnostics/runtimeEvidenceArtifacts.ts
811
- var import_node_crypto5 = require("crypto");
811
+ var import_node_crypto6 = require("crypto");
812
812
  var import_promises3 = __toESM(require("fs/promises"));
813
813
  var import_node_path3 = __toESM(require("path"));
814
814
 
815
815
  // src/runtimeStrategies.ts
816
816
  var import_jev8 = require("@tradejs/core/jev");
817
- var import_node_crypto4 = require("crypto");
817
+ var import_node_crypto5 = require("crypto");
818
818
  var import_promises2 = require("fs/promises");
819
819
  var import_node_path2 = __toESM(require("path"));
820
820
  var import_runtimeControls = require("@tradejs/infra/runtimeControls");
821
821
  var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
822
822
 
823
- // src/strategyRuntime.ts
824
- var import_jev6 = require("@tradejs/core/jev");
823
+ // src/strategy/portfolioRuntime.ts
824
+ var import_node_crypto2 = require("crypto");
825
+ var import_portfolio2 = require("@tradejs/core/portfolio");
826
+ var import_redis2 = require("@tradejs/infra/redis");
825
827
 
826
- // src/jev.ts
827
- var import_promises = __toESM(require("fs/promises"));
828
- var import_node_path = __toESM(require("path"));
829
- var import_jev4 = require("@tradejs/core/jev");
830
- var import_jev5 = require("@tradejs/infra/jev");
831
- var import_userSettings = require("@tradejs/infra/userSettings");
832
- init_jevInput();
833
- init_jevInput();
834
- init_jevTraining();
835
- var resolveJevProvider = async (userName) => {
836
- const settings = await (0, import_userSettings.getUserSettings)(userName);
837
- const provider = {
838
- endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
839
- model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
840
- };
841
- (0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
842
- if (!settings.JEV_API_KEY)
843
- throw new Error("Configure the Jev API key in Account settings");
844
- return provider;
845
- };
846
- var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
847
- var pending = /* @__PURE__ */ new Map();
848
- var pendingResponses = /* @__PURE__ */ new Map();
849
- var evaluateJevInput = async ({
850
- input,
851
- config,
852
- userName,
853
- projectRoot
854
- }) => {
855
- config = (0, import_jev4.parseJevConfig)(config);
856
- const inputHash = (0, import_jev5.jevHash)(input);
857
- const questions = questionsForJevInput(input);
858
- const questionsHash = (0, import_jev5.jevHash)(questions);
859
- const base = {
860
- questionSet: import_jev4.JEV_QUESTION_SET,
861
- schema: "tradejs-signal-assessment/v5",
862
- source: config.source,
863
- mode: config.mode,
864
- inputHash,
865
- questionsHash,
866
- geometryStatus: input.geometryStatus,
867
- levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
868
- };
869
- if (!input.questions.length)
870
- return {
871
- assessment: {
872
- ...base,
873
- status: "unavailable",
874
- model: "",
875
- scores: emptyScores(),
876
- confidence: emptyScores()
877
- }
878
- };
879
- if (config.source === "local") {
880
- const contents = await import_promises.default.readFile(
881
- import_node_path.default.resolve(projectRoot, config.modelFile),
882
- "utf8"
883
- );
884
- if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
885
- throw new Error("Local Jev gate checksum mismatch");
886
- const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
887
- const model = validateJevGateModel2(JSON.parse(contents));
888
- if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
889
- throw new Error("Local Jev gate strategy or question version mismatch");
890
- const scores = Object.fromEntries(
891
- import_jev4.JEV_DIMENSIONS.map((key) => [
892
- key,
893
- input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
894
- ])
895
- );
896
- return {
897
- assessment: {
898
- ...base,
899
- status: "available",
900
- model: config.modelSha256,
901
- scores,
902
- confidence: emptyScores()
828
+ // src/portfolio.ts
829
+ var import_portfolio = require("@tradejs/core/portfolio");
830
+ function createPortfolioCoordinator({
831
+ policy,
832
+ strategy,
833
+ store,
834
+ execution
835
+ }) {
836
+ const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
837
+ let cache;
838
+ async function transact(now2, change, force = false) {
839
+ for (let attempt = 0; attempt < 16; attempt++) {
840
+ const previous = await store.read();
841
+ if (previous && previous.fingerprint !== fingerprint3)
842
+ throw new Error(
843
+ "Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
844
+ );
845
+ const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
846
+ const bucket = Math.floor(now2 / policy.observationIntervalMs);
847
+ const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
848
+ const reconciled = (0, import_portfolio.reconcilePortfolio)(
849
+ policy,
850
+ strategy,
851
+ base,
852
+ observation
853
+ );
854
+ for (const intent of base.closing) {
855
+ if (!observation.positions.some(
856
+ (p) => p.symbol === intent.position.symbol
857
+ ))
858
+ await execution.confirmClosed?.(intent, observation.asOf);
903
859
  }
904
- };
905
- }
906
- const provider = config.provider ?? await resolveJevProvider(userName);
907
- const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
908
- const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
909
- const file = import_node_path.default.join(dir, "records", `${id}.json`);
910
- const getRecord = async () => {
911
- const existing = await (0, import_jev5.readJevArtifact)(file);
912
- if (existing) {
913
- 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))
914
- throw new Error("Jev recording identity mismatch");
915
- (0, import_jev5.validateJevResponse)(existing.response, input.questions);
916
- for (const key of import_jev4.JEV_DIMENSIONS) {
917
- const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
918
- if (existing.scores[key] !== expected)
919
- throw new Error("Jev recording scores do not match the response");
860
+ const changed = change(reconciled, observation);
861
+ const next = { ...changed.state, revision: base.revision + 1 };
862
+ if (await store.compare(previous, next)) {
863
+ cache = { revision: next.revision, bucket, observation };
864
+ return { state: next, observation, result: changed.result };
920
865
  }
921
- return existing;
866
+ cache = void 0;
922
867
  }
923
- if (config.source === "recorded")
924
- throw new Error(
925
- `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.`
868
+ throw new Error("Portfolio state contention; entry was not submitted");
869
+ }
870
+ async function tick(now2, force = false) {
871
+ const claimed = await transact(
872
+ now2,
873
+ (state, observation) => {
874
+ const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
875
+ return {
876
+ state: {
877
+ ...state,
878
+ closing: state.closing.map(
879
+ (c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
880
+ )
881
+ },
882
+ result: commands
883
+ };
884
+ },
885
+ force
886
+ );
887
+ for (const command of claimed.result)
888
+ await execution.close(
889
+ command.position,
890
+ command.intent,
891
+ claimed.observation.asOf
926
892
  );
927
- const settings = await (0, import_userSettings.getUserSettings)(userName);
928
- if (!settings.JEV_API_KEY)
929
- throw new Error("Configure the Jev API key in Account settings");
930
- const apiKey = settings.JEV_API_KEY;
931
- const configuredProvider = await resolveJevProvider(userName);
932
- if (configuredProvider.endpoint !== provider.endpoint)
933
- throw new Error(
934
- "Jev account endpoint changed; update the frozen provider configuration"
893
+ if (claimed.result.length) {
894
+ cache = void 0;
895
+ const refreshed = await transact(
896
+ now2,
897
+ (state) => ({ state, result: void 0 }),
898
+ true
899
+ );
900
+ return (0, import_portfolio.buildPortfolioContext)(
901
+ policy,
902
+ strategy,
903
+ refreshed.state,
904
+ refreshed.observation
935
905
  );
936
- const started = Date.now();
937
- const state = stateForJevInput(input);
938
- const responseId = (0, import_jev5.jevHash)({
939
- state,
940
- questionsHash,
941
- provider,
942
- user: (0, import_jev5.jevHash)(userName)
943
- });
944
- const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
945
- let responsePromise = pendingResponses.get(responseFile);
946
- if (!responsePromise) {
947
- responsePromise = (async () => {
948
- const saved = await (0, import_jev5.readJevArtifact)(responseFile);
949
- if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
950
- const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
951
- await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
952
- return fresh;
953
- })();
954
- pendingResponses.set(responseFile, responsePromise);
955
- }
956
- let response;
957
- try {
958
- response = await responsePromise;
959
- } finally {
960
- pendingResponses.delete(responseFile);
961
906
  }
962
- const scores = Object.fromEntries(
963
- import_jev4.JEV_DIMENSIONS.map((key) => [
964
- key,
965
- input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
966
- ])
907
+ return (0, import_portfolio.buildPortfolioContext)(
908
+ policy,
909
+ strategy,
910
+ claimed.state,
911
+ claimed.observation
967
912
  );
968
- const value = {
969
- schema: "tradejs-jev-record/v4",
970
- id,
971
- inputHash,
972
- questionsHash,
973
- provider,
974
- input,
975
- response,
976
- scores,
977
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
978
- elapsedMs: Date.now() - started
979
- };
980
- await (0, import_jev5.writeJevArtifact)(file, value);
981
- return await (0, import_jev5.readJevArtifact)(file);
982
- };
983
- const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
984
- let promise = pending.get(pendingKey);
985
- if (!promise) {
986
- promise = getRecord();
987
- pending.set(pendingKey, promise);
988
913
  }
989
- let record2;
990
- try {
991
- record2 = await promise;
992
- } finally {
993
- pending.delete(pendingKey);
914
+ async function updateReservation(now2, id, submitted, qty) {
915
+ return transact(
916
+ now2,
917
+ (s, o) => {
918
+ const exists2 = s.reservations.some((r) => r.id === id);
919
+ const blocked = submitted && (!exists2 || s.closing.length > 0 || o.asOf <= s.blockedThrough);
920
+ return {
921
+ state: {
922
+ ...s,
923
+ reservations: s.reservations.flatMap(
924
+ (r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
925
+ )
926
+ },
927
+ result: !blocked
928
+ };
929
+ },
930
+ true
931
+ );
994
932
  }
995
933
  return {
996
- record: record2,
997
- assessment: {
998
- ...base,
999
- status: "available",
1000
- recordId: id,
1001
- model: record2.response.model,
1002
- scores: record2.scores,
1003
- confidence: Object.fromEntries(
1004
- import_jev4.JEV_DIMENSIONS.map((key) => [
1005
- key,
1006
- record2.response.answers[key]?.confidence ?? null
1007
- ])
1008
- )
934
+ async context(now2) {
935
+ const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
936
+ if (state.fingerprint !== fingerprint3)
937
+ throw new Error("Portfolio policy changed under existing state");
938
+ const observation = await execution.observe(now2, state.reservations);
939
+ (0, import_portfolio.validatePortfolioObservation)(observation);
940
+ return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
941
+ },
942
+ tick,
943
+ async submit(now2, entry, send) {
944
+ await tick(now2);
945
+ const admitted = await transact(
946
+ now2,
947
+ (s, o) => {
948
+ const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
949
+ ...entry,
950
+ submitted: false,
951
+ oppositeHandled: false
952
+ });
953
+ return { state: r.state, result: r.reason };
954
+ },
955
+ true
956
+ );
957
+ if (admitted.result) return { reason: admitted.result };
958
+ let submitted = false;
959
+ try {
960
+ const value = await send(
961
+ async (qty) => {
962
+ if (!Number.isFinite(qty) || qty <= 0)
963
+ throw new Error("Invalid submitted portfolio quantity");
964
+ submitted = true;
965
+ const update = await updateReservation(now2, entry.id, true, qty);
966
+ if (!update.result)
967
+ throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
968
+ },
969
+ async () => {
970
+ await transact(
971
+ now2,
972
+ (s) => ({
973
+ state: {
974
+ ...s,
975
+ reservations: s.reservations.filter((r) => r.id !== entry.id),
976
+ completedSignals: {
977
+ ...s.completedSignals,
978
+ [entry.signalId]: now2
979
+ }
980
+ },
981
+ result: void 0
982
+ }),
983
+ true
984
+ );
985
+ }
986
+ );
987
+ if (!submitted) await updateReservation(now2, entry.id, false);
988
+ else await tick(now2, true);
989
+ return { value };
990
+ } catch (error) {
991
+ if (!submitted) await updateReservation(now2, entry.id, false);
992
+ throw error;
993
+ }
1009
994
  }
1010
995
  };
1011
- };
1012
- var assessSignalWithJev = async ({
1013
- signal,
1014
- config,
1015
- userName,
1016
- projectRoot,
1017
- strict,
1018
- scope = "runtime"
1019
- }) => {
1020
- let assessment;
1021
- try {
1022
- const input = buildJevInput(signal);
1023
- ({ assessment } = await evaluateJevInput({
1024
- input,
1025
- config,
1026
- userName,
1027
- projectRoot
1028
- }));
1029
- } catch (error) {
1030
- if (strict) throw error;
1031
- assessment = {
1032
- questionSet: import_jev4.JEV_QUESTION_SET,
1033
- schema: "tradejs-signal-assessment/v5",
1034
- source: config.source,
1035
- mode: config.mode,
1036
- status: "unavailable",
1037
- inputHash: "",
1038
- questionsHash: "",
1039
- geometryStatus: "absent",
1040
- levelsValid: null,
1041
- model: config.provider?.model ?? config.modelSha256 ?? "",
1042
- scores: emptyScores(),
1043
- confidence: emptyScores()
1044
- };
1045
- }
1046
- signal.assessment = assessment;
1047
- signal.additionalIndicators = {
1048
- ...signal.additionalIndicators,
1049
- jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
996
+ }
997
+ function createMemoryPortfolioStore() {
998
+ let state = null;
999
+ return {
1000
+ read: async () => state === null ? null : structuredClone(state),
1001
+ compare: async (expected, next) => {
1002
+ if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
1003
+ state = structuredClone(next);
1004
+ return true;
1005
+ }
1050
1006
  };
1051
- const identity = {
1052
- signalId: signal.signalId,
1053
- strategy: signal.strategy,
1054
- symbol: signal.symbol,
1055
- timestamp: signal.timestamp,
1056
- scope,
1057
- user: (0, import_jev5.jevHash)(userName),
1058
- config,
1059
- assessment
1060
- };
1061
- await (0, import_jev5.writeJevArtifact)(
1062
- import_node_path.default.resolve(
1063
- projectRoot,
1064
- config.recordsDir ?? "data/ai/jev",
1065
- "decisions",
1066
- `${(0, import_jev5.jevHash)(identity)}.json`
1067
- ),
1068
- identity
1069
- );
1070
- return assessment;
1071
- };
1072
-
1073
- // src/strategyRuntime.ts
1074
- var import_constants5 = require("@tradejs/core/constants");
1075
- var import_strategies6 = require("@tradejs/core/strategies");
1076
- var import_logger11 = require("@tradejs/infra/logger");
1077
-
1078
- // src/strategyHelpers/runtime.ts
1079
- var import_logger9 = require("@tradejs/infra/logger");
1080
- var import_constants3 = require("@tradejs/core/constants");
1081
- var import_ml2 = require("@tradejs/infra/ml");
1007
+ }
1082
1008
 
1083
- // src/strategy/entrySubmissionValidation.ts
1009
+ // src/runtimeJournal.ts
1010
+ var import_node_crypto = require("crypto");
1011
+ var import_constants = require("@tradejs/core/constants");
1012
+ var import_time = require("@tradejs/core/time");
1013
+ var import_trade = require("@tradejs/core/trade");
1084
1014
  var import_logger = require("@tradejs/infra/logger");
1085
-
1086
- // src/strategy/entryExecutionValidation.ts
1087
- var EntrySubmissionValidationError = class extends Error {
1088
- constructor() {
1089
- super(...arguments);
1090
- this.name = "EntrySubmissionValidationError";
1091
- }
1092
- };
1093
- var resolveEntryExecutionLimits = (config, direction) => {
1094
- const side = config[direction];
1095
- const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
1096
- const maximum = config.MAX_LOSS_VALUE;
1097
- if (minimum == null && maximum == null) return void 0;
1098
- const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
1099
- const maxLossValue = maximum == null ? void 0 : Number(maximum);
1100
- const feeRate = Number(config.RISK_FEE_RATE ?? 0);
1101
- const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
1102
- const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
1103
- const slippageBps = slippage + impact;
1104
- 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)
1105
- throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
1106
- return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
1107
- };
1108
- var validateEntryExecution = ({
1015
+ var import_redis = require("@tradejs/infra/redis");
1016
+ var now = () => Date.now();
1017
+ var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
1018
+ var calculateClosedPnl = ({
1109
1019
  direction,
1110
- price,
1111
- qty,
1112
- stopLossPrice,
1113
- takeProfitPrice,
1114
- limits
1020
+ entryPrice,
1021
+ exitPrice,
1022
+ qty
1115
1023
  }) => {
1116
- if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
1117
- throw new EntrySubmissionValidationError(
1118
- "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
1119
- );
1120
- const side = direction === "LONG" ? 1 : -1;
1121
- if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
1122
- throw new EntrySubmissionValidationError(
1123
- "INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
1124
- );
1125
- const slippage = limits.slippageBps / 1e4;
1126
- const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
1127
- const entryCost = cost(price, side);
1128
- const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
1129
- const rewardPerUnit = Math.max(
1130
- 0,
1131
- Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
1132
- );
1133
- const netRiskRatio = rewardPerUnit / lossPerUnit;
1134
- const plannedLossValue = qty * lossPerUnit;
1135
- if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
1136
- throw new EntrySubmissionValidationError(
1137
- "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
1138
- );
1139
- if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
1140
- throw new EntrySubmissionValidationError(
1141
- "ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
1142
- );
1143
- const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
1144
- return { netRiskRatio, plannedLossValue, riskExceeded };
1024
+ const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
1025
+ return Number.isFinite(pnl) ? pnl : null;
1145
1026
  };
1146
-
1147
- // src/strategy/entrySubmissionValidation.ts
1148
- var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
1149
- var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1150
- var getOrderArrivalSnapshot = async ({
1151
- connector,
1152
- symbol,
1153
- requireTimestamp = false
1154
- }) => {
1155
- if (typeof connector.getTopOfBookTicker !== "function") {
1156
- return {
1157
- arrivalSnapshotTime: Date.now(),
1158
- arrivalSource: "unavailable",
1159
- bid: null,
1160
- ask: null,
1161
- arrivalMid: null,
1162
- spreadBps: null
1163
- };
1027
+ var createRuntimeOrderId = (strategy) => {
1028
+ const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
1029
+ if (prefix === "tjs-") {
1030
+ return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
1031
+ }
1032
+ return `${prefix}${toRandomOrderSuffix()}`;
1033
+ };
1034
+ var recordRuntimeTradeOpen = async (params) => {
1035
+ const { userName } = params;
1036
+ if (!userName) {
1037
+ return null;
1164
1038
  }
1039
+ const record2 = {
1040
+ ...params,
1041
+ entryCount: 1,
1042
+ lastEntryPrice: params.entryPrice,
1043
+ lastEntryQty: params.qty,
1044
+ lastEntryTimestamp: params.entryTimestamp,
1045
+ status: "active",
1046
+ currentPrice: params.entryPrice,
1047
+ currentPnl: 0,
1048
+ closedPnl: null,
1049
+ exitPrice: null,
1050
+ exitTimestamp: null,
1051
+ lastSyncedAt: now()
1052
+ };
1053
+ const dayKey = (0, import_time.getRuntimeStorageDayKey)(record2.entryTimestamp);
1054
+ const runtimeScopeId = record2.deploymentId ?? record2.accountId;
1165
1055
  try {
1166
- const ticker = await connector.getTopOfBookTicker(symbol);
1167
- const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
1168
- const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
1169
- const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
1170
- const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
1171
- const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
1172
- const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
1173
- return {
1174
- arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
1175
- arrivalSource: "top_of_book",
1176
- bid,
1177
- ask,
1178
- arrivalMid,
1179
- spreadBps
1180
- };
1056
+ await Promise.all([
1057
+ (0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, record2.orderId), record2, {
1058
+ expire: 0
1059
+ }),
1060
+ (0, import_redis.setHashJsonField)(
1061
+ import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
1062
+ record2.orderId,
1063
+ record2,
1064
+ { expire: 0 }
1065
+ ),
1066
+ (0, import_redis.setData)(
1067
+ import_redis.redisKeys.runtimeActiveTrade(userName, record2.symbol, runtimeScopeId),
1068
+ { orderId: record2.orderId },
1069
+ { expire: 0 }
1070
+ )
1071
+ ]);
1181
1072
  } catch (error) {
1182
- import_logger.logger.warn(
1183
- "runtime order arrival snapshot failed: %s %s",
1184
- symbol,
1073
+ import_logger.logger.error(
1074
+ "runtime trade open journal failed: %s %s",
1075
+ record2.symbol,
1185
1076
  error?.message || String(error)
1186
1077
  );
1187
- return {
1188
- arrivalSnapshotTime: Date.now(),
1189
- arrivalSource: "top_of_book_error",
1190
- bid: null,
1191
- ask: null,
1192
- arrivalMid: null,
1193
- spreadBps: null
1194
- };
1195
1078
  }
1079
+ return record2;
1196
1080
  };
1197
- var validateEntryProtectionAtArrival = ({
1198
- direction,
1199
- signalPrice,
1200
- bid,
1201
- ask,
1202
- arrivalMid,
1203
- takeProfits,
1204
- stopLossPrice
1205
- }) => {
1206
- const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
1207
- const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
1208
- if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
1209
- throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
1210
- }
1211
- for (const takeProfit of takeProfits) {
1212
- if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
1213
- throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
1214
- }
1081
+ var recordRuntimeTradeIncrease = async (params) => {
1082
+ const {
1083
+ userName,
1084
+ strategy,
1085
+ symbol,
1086
+ direction,
1087
+ resultingQty,
1088
+ resultingEntryPrice,
1089
+ addedQty,
1090
+ addedEntryPrice,
1091
+ entryTimestamp,
1092
+ fee,
1093
+ accountId,
1094
+ deploymentId
1095
+ } = params;
1096
+ if (!userName) {
1097
+ return null;
1215
1098
  }
1216
- if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
1217
- throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
1099
+ const existing = await getActiveRuntimeTrade({
1100
+ userName,
1101
+ symbol,
1102
+ accountId,
1103
+ deploymentId
1104
+ });
1105
+ if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
1106
+ return null;
1218
1107
  }
1219
- };
1220
- var createEntrySubmissionValidation = ({
1221
- connector,
1222
- symbol,
1223
- direction,
1224
- takeProfits,
1225
- stopLossPrice,
1226
- limits,
1227
- onValidated
1228
- }) => {
1229
- if (!limits) return void 0;
1230
- if (!connector.capabilities?.orderSubmissionValidation)
1231
- throw new EntrySubmissionValidationError(
1232
- "ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
1233
- );
1234
- return async ({ qty, executionPrice }) => {
1235
- const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
1236
- const snapshot2 = simulated && executionPrice != null ? {
1237
- arrivalSnapshotTime: Date.now(),
1238
- arrivalSource: "simulation",
1239
- bid: executionPrice,
1240
- ask: executionPrice,
1241
- arrivalMid: executionPrice,
1242
- spreadBps: 0
1243
- } : await getOrderArrivalSnapshot({
1244
- connector,
1245
- symbol,
1246
- requireTimestamp: true
1247
- });
1248
- const age = Date.now() - snapshot2.arrivalSnapshotTime;
1249
- if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
1250
- throw new EntrySubmissionValidationError(
1251
- "ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
1252
- );
1253
- if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
1254
- throw new EntrySubmissionValidationError(
1255
- "ENTRY_QUOTE_STALE_AT_SUBMISSION"
1256
- );
1257
- const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
1258
- validateEntryProtectionAtArrival({
1259
- direction,
1260
- signalPrice: price,
1261
- bid: snapshot2.bid,
1262
- ask: snapshot2.ask,
1263
- arrivalMid: snapshot2.arrivalMid,
1264
- takeProfits,
1265
- stopLossPrice
1266
- });
1267
- const prices = takeProfits.map((tp) => tp.price);
1268
- const economics2 = validateEntryExecution({
1269
- direction,
1270
- price,
1271
- qty,
1272
- stopLossPrice,
1273
- takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
1274
- limits
1275
- });
1276
- onValidated?.(snapshot2, economics2);
1108
+ const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
1109
+ const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
1110
+ const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
1111
+ const next = {
1112
+ ...existing,
1113
+ ...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
1114
+ qty: resultingQty,
1115
+ entryPrice: resultingEntryPrice,
1116
+ entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
1117
+ lastEntryPrice: addedEntryPrice,
1118
+ lastEntryQty: addedQty,
1119
+ lastEntryTimestamp: entryTimestamp,
1120
+ currentPrice: resultingEntryPrice,
1121
+ currentPnl: 0,
1122
+ fee: openFee,
1123
+ openFee,
1124
+ totalFee,
1125
+ lastSyncedAt: now()
1277
1126
  };
1278
- };
1279
-
1280
- // src/ai.ts
1281
- var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
1282
- var import_redis = require("@tradejs/infra/redis");
1283
-
1284
- // src/aiShared.ts
1285
- var MAX_AI_SERIES_POINTS = 5;
1286
- var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
1287
- "tradejs.indicators.compactSnapshot"
1288
- );
1289
- var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
1290
- var trimSeriesDeep = (value) => {
1291
- if (Array.isArray(value)) {
1292
- const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
1293
- const isMatrix = trimmed.every((item) => Array.isArray(item));
1294
- if (isMatrix) {
1295
- return trimmed;
1296
- }
1297
- return trimmed.map(
1298
- (item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
1299
- );
1300
- }
1301
- if (value && typeof value === "object") {
1302
- return Object.fromEntries(
1303
- Object.entries(value).map(([key, nested]) => [
1304
- key,
1305
- trimSeriesDeep(nested)
1306
- ])
1127
+ const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
1128
+ try {
1129
+ await Promise.all([
1130
+ (0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, existing.orderId), next, {
1131
+ expire: 0
1132
+ }),
1133
+ (0, import_redis.setHashJsonField)(
1134
+ import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
1135
+ existing.orderId,
1136
+ next,
1137
+ { expire: 0 }
1138
+ )
1139
+ ]);
1140
+ } catch (error) {
1141
+ import_logger.logger.error(
1142
+ "runtime trade increase journal failed: %s %s",
1143
+ symbol,
1144
+ error?.message || String(error)
1307
1145
  );
1308
1146
  }
1309
- return value;
1147
+ return next;
1310
1148
  };
1311
- var buildCompactAiIndicatorsSnapshot = (value) => {
1312
- const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
1313
- if (typeof compactSnapshot === "function") {
1314
- return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
1149
+ var getActiveRuntimeTrade = async (params) => {
1150
+ const { userName, symbol, accountId, deploymentId } = params;
1151
+ if (!userName) {
1152
+ return null;
1315
1153
  }
1316
- return trimSeriesDeep(value);
1317
- };
1318
-
1319
- // src/aiMarketContext.ts
1320
- var toRecord = (value) => {
1321
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1154
+ const activeRef = await (0, import_redis.getData)(
1155
+ import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
1156
+ null
1157
+ );
1158
+ const orderId = String(activeRef?.orderId || "").trim();
1159
+ if (!orderId) {
1322
1160
  return null;
1323
1161
  }
1324
- return value;
1325
- };
1326
- var toFiniteNumber = (value) => {
1327
- const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
1328
- return Number.isFinite(numeric) ? numeric : null;
1329
- };
1330
- var roundTo = (value, decimals) => {
1331
- const factor = 10 ** decimals;
1332
- return Math.round(value * factor) / factor;
1333
- };
1334
- var buildMissingSpreadContext = () => ({
1335
- source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
1336
- indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
1337
- available: false,
1338
- value: null,
1339
- zScore: null,
1340
- bps: null,
1341
- absBps: null,
1342
- bias: null,
1343
- severity: null
1344
- });
1345
- var buildSpreadContextFromSignal = (signal) => {
1346
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1347
- const relative = toRecord(baseContext?.relative);
1348
- const execution = toRecord(relative?.execution);
1349
- const spread = toFiniteNumber(execution?.venueSpread);
1350
- const zScore = toFiniteNumber(execution?.venueSpreadZScore);
1351
- if (spread == null) {
1352
- return buildMissingSpreadContext();
1162
+ const existing = await (0, import_redis.getData)(
1163
+ import_redis.redisKeys.runtimeTrade(userName, orderId),
1164
+ null
1165
+ );
1166
+ if (!existing || existing.status !== "active") {
1167
+ await (0, import_redis.delKey)(
1168
+ import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
1169
+ );
1170
+ return null;
1353
1171
  }
1354
- const value = roundTo(spread, 8);
1355
- const bps = roundTo(value * 1e4, 2);
1356
- const absBps = Math.abs(bps);
1357
- const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
1358
- const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
1359
- return {
1360
- source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
1361
- indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
1362
- available: true,
1363
- value,
1364
- zScore,
1365
- bps,
1366
- absBps,
1367
- bias,
1368
- severity
1369
- };
1172
+ return existing;
1370
1173
  };
1371
- var buildTrueDeltaContextFromSignal = (signal) => {
1372
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1373
- const participation = toRecord(baseContext?.participation);
1374
- const delta = toRecord(participation?.delta);
1375
- const source = String(delta?.source ?? "");
1376
- const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
1377
- if (!delta || !isTrueDeltaSource) {
1378
- return {
1379
- source: source || null,
1380
- available: false,
1381
- buyPressurePct: null,
1382
- buyVolume: null,
1383
- sellVolume: null,
1384
- netDelta: null,
1385
- deltaPct: null,
1386
- signedVolumeZScore: null
1387
- };
1174
+ var markRuntimeTradeClosed = async (params) => {
1175
+ const {
1176
+ userName,
1177
+ expectedOrderId,
1178
+ symbol,
1179
+ strategy,
1180
+ exitPrice,
1181
+ exitTimestamp,
1182
+ closedPnl,
1183
+ exitType,
1184
+ accountId,
1185
+ deploymentId
1186
+ } = params;
1187
+ if (!userName) {
1188
+ return null;
1388
1189
  }
1389
- return {
1390
- source,
1391
- available: true,
1392
- buyPressurePct: toFiniteNumber(delta.buyPressurePct),
1393
- buyVolume: toFiniteNumber(delta.buyVolume),
1394
- sellVolume: toFiniteNumber(delta.sellVolume),
1395
- netDelta: toFiniteNumber(delta.netDelta),
1396
- deltaPct: toFiniteNumber(delta.deltaPct),
1397
- signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
1398
- };
1399
- };
1400
- var buildTradeFlowContextFromSignal = (signal) => {
1401
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1402
- const participation = toRecord(baseContext?.participation);
1403
- const tradeFlow = toRecord(participation?.tradeFlow);
1404
- if (!tradeFlow) {
1405
- return {
1406
- source: null,
1407
- available: false,
1408
- interval: null,
1409
- stale: null,
1410
- trades: null,
1411
- buyPressurePct: null,
1412
- netBaseDelta: null,
1413
- netQuoteDelta: null
1414
- };
1190
+ const existing = expectedOrderId ? await (0, import_redis.getDataStrict)(
1191
+ import_redis.redisKeys.runtimeTrade(userName, expectedOrderId)
1192
+ ) : await getActiveRuntimeTrade({
1193
+ userName,
1194
+ symbol,
1195
+ accountId,
1196
+ deploymentId
1197
+ });
1198
+ if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
1199
+ return null;
1415
1200
  }
1416
- return {
1417
- source: String(tradeFlow.source ?? ""),
1418
- available: true,
1419
- interval: String(tradeFlow.interval ?? ""),
1420
- stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
1421
- trades: toFiniteNumber(tradeFlow.trades),
1422
- buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
1423
- netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
1424
- netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
1201
+ const orderId = existing.orderId;
1202
+ if (strategy && existing.strategy !== strategy) {
1203
+ return null;
1204
+ }
1205
+ const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
1206
+ const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
1207
+ direction: existing.direction,
1208
+ entryPrice: existing.entryPrice,
1209
+ exitPrice: resolvedExitPrice,
1210
+ qty: existing.qty
1211
+ }) : existing.closedPnl ?? existing.currentPnl ?? null;
1212
+ const next = expectedOrderId && existing.status === "closed" ? existing : {
1213
+ ...existing,
1214
+ status: "closed",
1215
+ currentPrice: resolvedExitPrice,
1216
+ currentPnl: resolvedClosedPnl,
1217
+ closedPnl: resolvedClosedPnl,
1218
+ exitPrice: resolvedExitPrice,
1219
+ exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
1220
+ exitType: exitType ?? existing.exitType ?? null,
1221
+ lastSyncedAt: now()
1425
1222
  };
1426
- };
1427
- var buildMarketBreadthContextFromSignal = (signal) => {
1428
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1429
- const relative = toRecord(baseContext?.relative);
1430
- const breadth = toRecord(relative?.marketBreadth);
1431
- if (!breadth) {
1432
- return {
1433
- source: null,
1434
- available: false,
1435
- universe: null,
1436
- interval: null,
1437
- stale: null,
1438
- symbolsCount: null,
1439
- advanceDeclineRatio: null,
1440
- pctAboveMa20: null,
1441
- pctAboveMa50: null,
1442
- equalWeightedReturn: null,
1443
- volumeWeightedReturn: null,
1444
- dispersion: null
1445
- };
1223
+ const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
1224
+ const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
1225
+ try {
1226
+ await Promise.all([
1227
+ (expectedOrderId ? import_redis.setDataStrict : import_redis.setData)(
1228
+ import_redis.redisKeys.runtimeTrade(userName, orderId),
1229
+ next,
1230
+ {
1231
+ expire: import_constants.TTL_1M
1232
+ }
1233
+ ),
1234
+ (expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
1235
+ import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
1236
+ orderId,
1237
+ next,
1238
+ { expire: import_constants.TTL_1M }
1239
+ ),
1240
+ (expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
1241
+ import_redis.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
1242
+ orderId,
1243
+ next,
1244
+ { expire: import_constants.TTL_1M }
1245
+ ),
1246
+ expectedOrderId ? (0, import_redis.deleteRuntimeTradeReference)(
1247
+ import_redis.redisKeys.runtimeActiveTrade(
1248
+ userName,
1249
+ symbol,
1250
+ deploymentId ?? accountId
1251
+ ),
1252
+ expectedOrderId
1253
+ ) : (0, import_redis.delKey)(
1254
+ import_redis.redisKeys.runtimeActiveTrade(
1255
+ userName,
1256
+ symbol,
1257
+ deploymentId ?? accountId
1258
+ )
1259
+ )
1260
+ ]);
1261
+ } catch (error) {
1262
+ if (expectedOrderId) throw error;
1263
+ import_logger.logger.error(
1264
+ "runtime trade close journal failed: %s %s",
1265
+ symbol,
1266
+ error?.message || String(error)
1267
+ );
1446
1268
  }
1447
- return {
1448
- source: String(breadth.source ?? ""),
1449
- available: true,
1450
- universe: String(breadth.universe ?? ""),
1451
- interval: String(breadth.interval ?? ""),
1452
- stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
1453
- symbolsCount: toFiniteNumber(breadth.symbolsCount),
1454
- advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
1455
- pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
1456
- pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
1457
- equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
1458
- volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
1459
- dispersion: toFiniteNumber(breadth.dispersion)
1460
- };
1269
+ return next;
1461
1270
  };
1462
- var buildMarketBreadthsContextFromSignal = (signal) => {
1463
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1464
- const relative = toRecord(baseContext?.relative);
1465
- const breadths = toRecord(relative?.marketBreadths);
1466
- const build = (key) => {
1467
- const breadth = toRecord(breadths?.[key]);
1468
- if (!breadth) {
1271
+
1272
+ // src/strategy/portfolioRuntime.ts
1273
+ var coordinators = /* @__PURE__ */ new WeakMap();
1274
+ async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
1275
+ if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
1276
+ throw new Error(
1277
+ "Portfolio policies require position marks and entry executions"
1278
+ );
1279
+ const orders = await Promise.all(
1280
+ reservations.map(async (r) => {
1281
+ if (r.submitted && connector.getEntryOrderObservation) {
1282
+ const fact2 = await connector.getEntryOrderObservation({
1283
+ symbol: r.symbol,
1284
+ orderId: r.id
1285
+ });
1286
+ if (fact2) return fact2;
1287
+ }
1288
+ const executions = r.submitted ? await connector.getEntryExecutions({
1289
+ symbol: r.symbol,
1290
+ startTime: r.timestamp,
1291
+ endTime: asOf
1292
+ }) : [];
1293
+ const filledQty = executions.filter(
1294
+ (e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
1295
+ ).reduce((sum, e) => sum + e.qty, 0);
1469
1296
  return {
1470
- source: null,
1471
- available: false,
1472
- universe: null,
1473
- interval: null,
1474
- stale: null,
1475
- symbolsCount: null,
1476
- advanceDeclineRatio: null,
1477
- pctAboveMa20: null,
1478
- pctAboveMa50: null,
1479
- equalWeightedReturn: null,
1480
- volumeWeightedReturn: null,
1481
- dispersion: null
1297
+ id: r.id,
1298
+ status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
1299
+ filledQty,
1300
+ positionId: r.id
1301
+ };
1302
+ })
1303
+ );
1304
+ const snapshots2 = await connector.getOpenPositionPnl();
1305
+ const positions = await Promise.all(
1306
+ snapshots2.map(async (p) => {
1307
+ const reserved = reservations.find(
1308
+ (r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
1309
+ (o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
1310
+ )
1311
+ );
1312
+ const trade = await getActiveRuntimeTrade({
1313
+ ...binding,
1314
+ symbol: p.symbol
1315
+ });
1316
+ 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;
1317
+ return {
1318
+ id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
1319
+ strategy: reserved?.strategy ?? owner?.strategy ?? null,
1320
+ symbol: p.symbol,
1321
+ direction: p.direction,
1322
+ qty: p.qty,
1323
+ entryPrice: p.price,
1324
+ markPrice: p.currentPrice,
1325
+ markedAt: p.observedAt ?? null,
1326
+ stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
1482
1327
  };
1328
+ })
1329
+ );
1330
+ return { asOf: Math.max(asOf, observedNow()), positions, orders };
1331
+ }
1332
+ function persistentStore(key) {
1333
+ const raw = /* @__PURE__ */ new WeakMap();
1334
+ return {
1335
+ async read() {
1336
+ const value = await import_redis2.portfolioStateStorage.read(key);
1337
+ if (value === null) return null;
1338
+ const state = JSON.parse(value);
1339
+ (0, import_portfolio2.assertPortfolioState)(state);
1340
+ raw.set(state, value);
1341
+ return state;
1342
+ },
1343
+ async compare(previous, next) {
1344
+ const expected = previous === null ? null : raw.get(previous);
1345
+ if (expected === void 0)
1346
+ throw new Error("Portfolio CAS requires a storage snapshot");
1347
+ return import_redis2.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
1483
1348
  }
1484
- return {
1485
- source: String(breadth.source ?? ""),
1486
- available: true,
1487
- universe: String(breadth.universe ?? ""),
1488
- interval: String(breadth.interval ?? ""),
1489
- stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
1490
- symbolsCount: toFiniteNumber(breadth.symbolsCount),
1491
- advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
1492
- pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
1493
- pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
1494
- equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
1495
- volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
1496
- dispersion: toFiniteNumber(breadth.dispersion)
1497
- };
1498
1349
  };
1499
- return {
1500
- top5: build("top5"),
1501
- top10: build("top10"),
1502
- top30: build("top30"),
1503
- top50: build("top50"),
1504
- top100: build("top100")
1350
+ }
1351
+ function getPortfolioRuntime({
1352
+ value,
1353
+ connectorName,
1354
+ strategy,
1355
+ connector,
1356
+ env,
1357
+ userName,
1358
+ accountId,
1359
+ deploymentId
1360
+ }) {
1361
+ const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
1362
+ if (!policy) return void 0;
1363
+ const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
1364
+ if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
1365
+ throw new Error(
1366
+ "Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
1367
+ );
1368
+ if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
1369
+ throw new Error(
1370
+ "Portfolio runtime requires exact user/account/deployment binding, marks and executions"
1371
+ );
1372
+ const binding = {
1373
+ userName,
1374
+ accountId,
1375
+ deploymentId
1505
1376
  };
1377
+ const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
1378
+ const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
1379
+ const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
1380
+ const cacheKey = key;
1381
+ let registry = coordinators.get(connector);
1382
+ if (!registry) {
1383
+ registry = /* @__PURE__ */ new Map();
1384
+ coordinators.set(connector, registry);
1385
+ }
1386
+ const existing = registry.get(cacheKey);
1387
+ if (existing) {
1388
+ if (existing.fingerprint !== fingerprint3)
1389
+ throw new Error("Conflicting portfolio configuration for the same scope");
1390
+ return existing.coordinator;
1391
+ }
1392
+ const coordinator = createPortfolioCoordinator({
1393
+ policy,
1394
+ strategy,
1395
+ store: replay ? createMemoryPortfolioStore() : persistentStore(key),
1396
+ execution: {
1397
+ async confirmClosed(intent, asOf) {
1398
+ if (replay) return;
1399
+ const position = intent.position;
1400
+ await markRuntimeTradeClosed({
1401
+ ...binding,
1402
+ strategy: position.strategy,
1403
+ expectedOrderId: position.id,
1404
+ symbol: position.symbol,
1405
+ exitPrice: position.markPrice,
1406
+ exitTimestamp: asOf,
1407
+ exitType: "exit"
1408
+ });
1409
+ },
1410
+ observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
1411
+ async close(position, intent, asOf) {
1412
+ if (!replay) {
1413
+ const trade = await getActiveRuntimeTrade({
1414
+ ...binding,
1415
+ symbol: position.symbol
1416
+ });
1417
+ if (!trade || trade.orderId !== position.id)
1418
+ throw new Error("Portfolio close ownership changed");
1419
+ }
1420
+ await connector.closePosition({
1421
+ symbol: position.symbol,
1422
+ direction: position.direction,
1423
+ price: position.markPrice,
1424
+ timestamp: asOf,
1425
+ signal: {
1426
+ signalId: `${position.id}:${intent.reason}`,
1427
+ strategy: position.strategy,
1428
+ symbol: position.symbol,
1429
+ direction: position.direction,
1430
+ interval: "1",
1431
+ timestamp: asOf,
1432
+ prices: {
1433
+ riskRatio: 0,
1434
+ currentPrice: position.markPrice,
1435
+ takeProfitPrice: position.markPrice,
1436
+ stopLossPrice: position.markPrice
1437
+ },
1438
+ figures: {},
1439
+ indicators: {},
1440
+ additionalIndicators: { portfolioExitReason: intent.reason }
1441
+ }
1442
+ });
1443
+ }
1444
+ }
1445
+ });
1446
+ registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
1447
+ return coordinator;
1448
+ }
1449
+
1450
+ // src/strategyHelpers/tickerProfiles.ts
1451
+ var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
1452
+ var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
1453
+
1454
+ // src/tradejsConfig.ts
1455
+ var import_fs = __toESM(require("fs"));
1456
+ var import_path = __toESM(require("path"));
1457
+ var import_url = require("url");
1458
+ var import_config = require("@tradejs/core/config");
1459
+ var import_logger2 = require("@tradejs/infra/logger");
1460
+ var CONFIG_FILE_NAMES = [
1461
+ "tradejs.config.ts",
1462
+ "tradejs.config.mts",
1463
+ "tradejs.config.js",
1464
+ "tradejs.config.mjs",
1465
+ "tradejs.config.cjs"
1466
+ ];
1467
+ var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
1468
+ var cachedByCwd = /* @__PURE__ */ new Map();
1469
+ var announcedConfigFile = /* @__PURE__ */ new Set();
1470
+ var tsNodeRegistered = false;
1471
+ var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
1472
+ var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
1473
+ var getTradejsProjectCwd = (cwd) => {
1474
+ const explicit = String(cwd ?? "").trim();
1475
+ if (explicit) {
1476
+ return import_path.default.resolve(explicit);
1477
+ }
1478
+ const fromEnv = String(process.env.PROJECT_CWD || "").trim();
1479
+ if (fromEnv) {
1480
+ return import_path.default.resolve(fromEnv);
1481
+ }
1482
+ return process.cwd();
1506
1483
  };
1507
- var buildTargetVsBtcContextFromSignal = (signal) => {
1508
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1509
- const relative = toRecord(baseContext?.relative);
1510
- const targetVsBtc = toRecord(relative?.targetVsBtc);
1511
- if (!targetVsBtc) {
1512
- return {
1513
- source: null,
1514
- available: false,
1515
- ratioReturn1h: null,
1516
- ratioReturn4h: null,
1517
- ratioReturn24h: null,
1518
- alphaVsBtc1h: null,
1519
- alphaVsBtc4h: null,
1520
- alphaVsBtc24h: null,
1521
- betaToBtc20: null,
1522
- correlationToBtc20: null,
1523
- ratioTrend: null
1524
- };
1484
+ var normalizeConfig = (rawConfig) => {
1485
+ if (!rawConfig || typeof rawConfig !== "object") {
1486
+ return {};
1525
1487
  }
1488
+ const config = rawConfig;
1489
+ const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
1490
+ const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
1491
+ const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
1492
+ const hooks = (0, import_config.normalizeTradejsConfigHooks)(
1493
+ config.hooks
1494
+ );
1495
+ const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
1526
1496
  return {
1527
- source: String(targetVsBtc.source ?? ""),
1528
- available: true,
1529
- ratioReturn1h: toFiniteNumber(targetVsBtc.ratioReturn1h),
1530
- ratioReturn4h: toFiniteNumber(targetVsBtc.ratioReturn4h),
1531
- ratioReturn24h: toFiniteNumber(targetVsBtc.ratioReturn24h),
1532
- alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
1533
- alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
1534
- alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
1535
- betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
1536
- correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
1537
- ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
1497
+ strategies: strategies2,
1498
+ indicators,
1499
+ connectors,
1500
+ ...hooks ? { hooks } : {},
1501
+ ...runtime ? { runtime } : {}
1538
1502
  };
1539
1503
  };
1540
- var buildBtcAltRegimeContextFromSignal = (signal) => {
1541
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1542
- const relative = toRecord(baseContext?.relative);
1543
- const btcAltRegime = toRecord(relative?.btcAltRegime);
1544
- if (!btcAltRegime) {
1545
- return {
1546
- source: null,
1547
- available: false,
1548
- universe: null,
1549
- interval: null,
1550
- stale: null,
1551
- regime: null,
1552
- btcReturn24h: null,
1553
- altBasketReturn24h: null,
1554
- btcVsAltReturn24h: null,
1555
- btcTurnoverShare24h: null,
1556
- btcTurnoverShareChange24h: null,
1557
- altVolToBtcVol24h: null,
1558
- altDispersion24h: null
1559
- };
1504
+ var getNodeCreateRequire = () => {
1505
+ const builtinModule = process.getBuiltinModule?.("module");
1506
+ if (typeof builtinModule?.createRequire === "function") {
1507
+ return builtinModule.createRequire;
1560
1508
  }
1561
- return {
1562
- source: String(btcAltRegime.source ?? ""),
1563
- available: true,
1564
- universe: String(btcAltRegime.universe ?? ""),
1565
- interval: String(btcAltRegime.interval ?? ""),
1566
- stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
1567
- regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
1568
- btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
1569
- altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
1570
- btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
1571
- btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
1572
- btcTurnoverShareChange24h: toFiniteNumber(
1573
- btcAltRegime.btcTurnoverShareChange24h
1574
- ),
1575
- altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
1576
- altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
1577
- };
1509
+ throw new TypeError("module.createRequire is not available");
1578
1510
  };
1579
- var buildCmcGlobalContextFromSignal = (signal) => {
1580
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1581
- const relative = toRecord(baseContext?.relative);
1582
- const cmcGlobal = toRecord(relative?.cmcGlobal);
1583
- if (!cmcGlobal) {
1584
- return {
1585
- source: null,
1586
- available: false,
1587
- interval: null,
1588
- asOfTs: null,
1589
- stale: null,
1590
- totalMarketCapUsd: null,
1591
- totalVolumeUsd: null,
1592
- totalVolumeReportedUsd: null,
1593
- altMarketCapUsd: null,
1594
- altVolumeUsd: null,
1595
- altVolumeReportedUsd: null,
1596
- btcDominancePct: null,
1597
- ethDominancePct: null,
1598
- btcDominanceChange24hPct: null,
1599
- ethDominanceChange24hPct: null,
1600
- altMarketCapChange24hPct: null,
1601
- altVolumeChange24hPct: null,
1602
- activeCryptocurrencies: null,
1603
- activeExchanges: null,
1604
- activeMarketPairs: null,
1605
- altLiquidityRegime: null
1606
- };
1511
+ var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
1512
+ var ensureTsNodeRegistered = async () => {
1513
+ if (tsNodeRegistered) {
1514
+ return;
1515
+ }
1516
+ const tsNodeModule = await import("ts-node");
1517
+ const tsNode = tsNodeModule.default ?? tsNodeModule;
1518
+ tsNode.register?.({
1519
+ transpileOnly: true,
1520
+ compilerOptions: {
1521
+ module: "Node16",
1522
+ moduleResolution: "node16"
1523
+ }
1524
+ });
1525
+ tsNodeRegistered = true;
1526
+ };
1527
+ var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
1528
+ const projectRoot = getTradejsProjectCwd(cwd);
1529
+ if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
1530
+ return;
1531
+ }
1532
+ const tsconfigPathsModule = await import("tsconfig-paths");
1533
+ const loadConfig = tsconfigPathsModule.loadConfig;
1534
+ const register = tsconfigPathsModule.register;
1535
+ if (typeof loadConfig !== "function" || typeof register !== "function") {
1536
+ return;
1537
+ }
1538
+ const loadedConfig = loadConfig(projectRoot);
1539
+ if (loadedConfig.resultType !== "success") {
1540
+ return;
1541
+ }
1542
+ register({
1543
+ baseUrl: loadedConfig.absoluteBaseUrl,
1544
+ paths: loadedConfig.paths,
1545
+ addMatchAll: false
1546
+ });
1547
+ tsconfigPathsRegisteredByCwd.add(projectRoot);
1548
+ };
1549
+ var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
1550
+ const projectRoot = getTradejsProjectCwd(cwd);
1551
+ const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
1552
+ if (cachedMatcher) {
1553
+ const resolved2 = cachedMatcher(moduleName);
1554
+ return resolved2 || null;
1555
+ }
1556
+ const tsconfigPathsModule = await import("tsconfig-paths");
1557
+ const loadConfig = tsconfigPathsModule.loadConfig;
1558
+ const createMatchPath = tsconfigPathsModule.createMatchPath;
1559
+ if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
1560
+ return null;
1561
+ }
1562
+ const loadedConfig = loadConfig(projectRoot);
1563
+ if (loadedConfig.resultType !== "success") {
1564
+ return null;
1565
+ }
1566
+ const matchPath = createMatchPath(
1567
+ loadedConfig.absoluteBaseUrl,
1568
+ loadedConfig.paths
1569
+ );
1570
+ const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
1571
+ ".ts",
1572
+ ".tsx",
1573
+ ".mts",
1574
+ ".cts",
1575
+ ".js",
1576
+ ".jsx",
1577
+ ".mjs",
1578
+ ".cjs",
1579
+ ".json"
1580
+ ]) || "";
1581
+ tsconfigPathMatchersByCwd.set(projectRoot, matcher);
1582
+ const resolved = matcher(moduleName);
1583
+ return resolved || null;
1584
+ };
1585
+ var toImportSpecifier = (moduleName) => {
1586
+ if (moduleName.startsWith("file://")) {
1587
+ return moduleName;
1588
+ }
1589
+ if (import_path.default.isAbsolute(moduleName)) {
1590
+ return (0, import_url.pathToFileURL)(moduleName).href;
1591
+ }
1592
+ return moduleName;
1593
+ };
1594
+ var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
1595
+ var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
1596
+ var isBareModuleSpecifier = (moduleName) => {
1597
+ const normalized = String(moduleName ?? "").trim();
1598
+ if (!normalized) {
1599
+ return false;
1600
+ }
1601
+ if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
1602
+ return false;
1603
+ }
1604
+ return true;
1605
+ };
1606
+ var importConfigFile = async (configFilePath) => {
1607
+ const ext = import_path.default.extname(configFilePath).toLowerCase();
1608
+ const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
1609
+ if (ext === ".ts" || ext === ".mts") {
1610
+ const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
1611
+ await ensureTsNodeRegistered();
1612
+ await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
1613
+ return requireFn(configFilePath);
1614
+ }
1615
+ return import(
1616
+ /* webpackIgnore: true */
1617
+ configFileUrl
1618
+ );
1619
+ };
1620
+ var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
1621
+ const normalized = String(moduleName ?? "").trim();
1622
+ if (!normalized) {
1623
+ return {};
1624
+ }
1625
+ let modulePath = normalized;
1626
+ if (normalized.startsWith("file://")) {
1627
+ try {
1628
+ modulePath = (0, import_url.fileURLToPath)(normalized);
1629
+ } catch {
1630
+ modulePath = normalized;
1631
+ }
1632
+ }
1633
+ const requireFn = getRequireFn(
1634
+ import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
1635
+ );
1636
+ if (isTsModulePath(modulePath)) {
1637
+ await ensureTsNodeRegistered();
1638
+ await ensureTsconfigPathsRegistered(cwd);
1639
+ return requireFn(modulePath);
1640
+ }
1641
+ if (isBareModuleSpecifier(normalized)) {
1642
+ await ensureTsconfigPathsRegistered(cwd);
1643
+ try {
1644
+ return requireFn(normalized);
1645
+ } catch (error) {
1646
+ const resolvedByTsconfig = await resolveTsconfigPathModule(
1647
+ normalized,
1648
+ cwd
1649
+ );
1650
+ if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
1651
+ return requireFn(resolvedByTsconfig);
1652
+ }
1653
+ throw error;
1654
+ }
1655
+ }
1656
+ try {
1657
+ return await import(
1658
+ /* webpackIgnore: true */
1659
+ toImportSpecifier(normalized)
1660
+ );
1661
+ } catch (error) {
1662
+ if (isTsModulePath(modulePath)) {
1663
+ await ensureTsNodeRegistered();
1664
+ await ensureTsconfigPathsRegistered(cwd);
1665
+ return requireFn(modulePath);
1666
+ }
1667
+ throw error;
1668
+ }
1669
+ };
1670
+ var resolveExportedConfig = (moduleExports) => {
1671
+ const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
1672
+ return normalizeConfig(candidate);
1673
+ };
1674
+ var findConfigFilePath = (cwd) => {
1675
+ let currentDir = import_path.default.resolve(cwd);
1676
+ while (true) {
1677
+ for (const fileName of CONFIG_FILE_NAMES) {
1678
+ const fullPath = import_path.default.join(currentDir, fileName);
1679
+ if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
1680
+ return fullPath;
1681
+ }
1682
+ }
1683
+ const parentDir = import_path.default.dirname(currentDir);
1684
+ if (parentDir === currentDir) {
1685
+ return null;
1686
+ }
1687
+ currentDir = parentDir;
1688
+ }
1689
+ };
1690
+ var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
1691
+ const normalized = String(moduleName ?? "").trim();
1692
+ if (!normalized) {
1693
+ return "";
1694
+ }
1695
+ if (normalized.startsWith("file://")) {
1696
+ try {
1697
+ return (0, import_url.fileURLToPath)(normalized);
1698
+ } catch {
1699
+ return normalized;
1700
+ }
1701
+ }
1702
+ if (import_path.default.isAbsolute(normalized)) {
1703
+ return normalized;
1704
+ }
1705
+ if (isRelativeModulePath(normalized)) {
1706
+ return import_path.default.resolve(cwd, normalized);
1707
+ }
1708
+ return normalized;
1709
+ };
1710
+ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
1711
+ const cached = cachedByCwd.get(cwd);
1712
+ if (cached) {
1713
+ return cached;
1714
+ }
1715
+ const configFilePath = findConfigFilePath(cwd);
1716
+ if (!configFilePath) {
1717
+ cachedByCwd.set(cwd, {});
1718
+ return {};
1719
+ }
1720
+ try {
1721
+ const moduleExports = await importConfigFile(configFilePath);
1722
+ const config = resolveExportedConfig(moduleExports);
1723
+ cachedByCwd.set(cwd, config);
1724
+ if (!announcedConfigFile.has(configFilePath)) {
1725
+ announcedConfigFile.add(configFilePath);
1726
+ import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
1727
+ }
1728
+ return config;
1729
+ } catch (error) {
1730
+ import_logger2.logger.log(
1731
+ "warn",
1732
+ "Failed to load TradeJS config from %s: %s",
1733
+ configFilePath,
1734
+ String(error)
1735
+ );
1736
+ cachedByCwd.set(cwd, {});
1737
+ return {};
1738
+ }
1739
+ };
1740
+
1741
+ // src/strategy/manifests.ts
1742
+ var import_indicators = require("@tradejs/core/indicators");
1743
+ var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
1744
+ var sharedRegistryScope = globalThis;
1745
+ var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
1746
+ registryStateByProjectRoot: /* @__PURE__ */ new Map()
1747
+ });
1748
+ var createStrategyRegistryState = () => ({
1749
+ strategyCreators: /* @__PURE__ */ new Map(),
1750
+ strategyManifestsMap: /* @__PURE__ */ new Map(),
1751
+ strategyEntriesMap: /* @__PURE__ */ new Map(),
1752
+ strategySourcesMap: /* @__PURE__ */ new Map(),
1753
+ strategyModuleResolutions: /* @__PURE__ */ new Map(),
1754
+ pluginsLoadPromise: null
1755
+ });
1756
+ var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
1757
+ var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
1758
+ const projectRoot = getTradejsProjectCwd(cwd);
1759
+ let state = registryStateByProjectRoot.get(projectRoot);
1760
+ if (!state) {
1761
+ state = createStrategyRegistryState();
1762
+ registryStateByProjectRoot.set(projectRoot, state);
1607
1763
  }
1608
1764
  return {
1609
- source: String(cmcGlobal.source ?? ""),
1610
- available: true,
1611
- interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
1612
- asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
1613
- stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
1614
- totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
1615
- totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
1616
- totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
1617
- altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
1618
- altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
1619
- altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
1620
- btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
1621
- ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
1622
- btcDominanceChange24hPct: toFiniteNumber(
1623
- cmcGlobal.btcDominanceChange24hPct
1624
- ),
1625
- ethDominanceChange24hPct: toFiniteNumber(
1626
- cmcGlobal.ethDominanceChange24hPct
1627
- ),
1628
- altMarketCapChange24hPct: toFiniteNumber(
1629
- cmcGlobal.altMarketCapChange24hPct
1630
- ),
1631
- altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
1632
- activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
1633
- activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
1634
- activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
1635
- altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
1765
+ projectRoot,
1766
+ state
1636
1767
  };
1637
1768
  };
1638
- var buildCmcReferenceAssetsContextFromSignal = (signal) => {
1639
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1640
- const relative = toRecord(baseContext?.relative);
1641
- const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
1642
- if (!cmcReferenceAssets) {
1643
- return {
1644
- source: null,
1645
- available: false,
1646
- interval: null,
1647
- asOfTs: null,
1648
- stale: null,
1649
- btcMarketCapUsd: null,
1650
- ethMarketCapUsd: null,
1651
- btcVolumeUsd: null,
1652
- ethVolumeUsd: null,
1653
- btcVolumeToMarketCap: null,
1654
- ethVolumeToMarketCap: null,
1655
- ethBtcMarketCapRatio: null,
1656
- ethBtcMarketCapRatioChange24hPct: null,
1657
- ethVsBtcVolumeRatio: null,
1658
- referenceLiquidityRegime: null
1659
- };
1660
- }
1769
+ var toUniqueModules = (modules = []) => [
1770
+ ...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
1771
+ ];
1772
+ var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
1773
+ const config = await loadTradejsConfig(cwd);
1661
1774
  return {
1662
- source: String(cmcReferenceAssets.source ?? ""),
1663
- available: true,
1664
- interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
1665
- asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
1666
- stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
1667
- btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
1668
- ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
1669
- btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
1670
- ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
1671
- btcVolumeToMarketCap: toFiniteNumber(
1672
- cmcReferenceAssets.btcVolumeToMarketCap
1673
- ),
1674
- ethVolumeToMarketCap: toFiniteNumber(
1675
- cmcReferenceAssets.ethVolumeToMarketCap
1676
- ),
1677
- ethBtcMarketCapRatio: toFiniteNumber(
1678
- cmcReferenceAssets.ethBtcMarketCapRatio
1679
- ),
1680
- ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
1681
- cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
1682
- ),
1683
- ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
1684
- referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
1775
+ strategyModules: toUniqueModules(config.strategies),
1776
+ indicatorModules: toUniqueModules(config.indicators)
1685
1777
  };
1686
1778
  };
1687
- var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
1688
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1689
- const relative = toRecord(baseContext?.relative);
1690
- const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
1691
- if (!cmcExchangeLiquidity) {
1779
+ var extractModuleEntries = (moduleExport, key) => {
1780
+ if (!moduleExport || typeof moduleExport !== "object") {
1781
+ return null;
1782
+ }
1783
+ const candidate = moduleExport;
1784
+ if (Array.isArray(candidate[key])) {
1785
+ return candidate[key];
1786
+ }
1787
+ const defaultExport = candidate.default;
1788
+ if (defaultExport && Array.isArray(defaultExport[key])) {
1789
+ return defaultExport[key];
1790
+ }
1791
+ return null;
1792
+ };
1793
+ var extractStrategyPluginDefinition = (moduleExport) => {
1794
+ const strategyEntries = extractModuleEntries(
1795
+ moduleExport,
1796
+ "strategyEntries"
1797
+ );
1798
+ return strategyEntries ? { strategyEntries } : null;
1799
+ };
1800
+ var extractIndicatorPluginDefinition = (moduleExport) => {
1801
+ const indicatorEntries = extractModuleEntries(
1802
+ moduleExport,
1803
+ "indicatorEntries"
1804
+ );
1805
+ return indicatorEntries ? { indicatorEntries } : null;
1806
+ };
1807
+ var validateStrategyEntries = (moduleName, entries, state) => {
1808
+ const issues = [];
1809
+ const names = /* @__PURE__ */ new Set();
1810
+ entries.forEach((entry, index) => {
1811
+ const entryPath = `${moduleName}.strategyEntries[${index}]`;
1812
+ const strategyName = entry?.manifest?.name;
1813
+ if (typeof strategyName !== "string" || !strategyName.trim()) {
1814
+ issues.push(`${entryPath}: manifest.name is required`);
1815
+ } else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
1816
+ issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
1817
+ } else {
1818
+ names.add(strategyName);
1819
+ }
1820
+ if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
1821
+ issues.push(`${entryPath}: defaults must be an object`);
1822
+ }
1823
+ if (typeof entry?.parseConfig !== "function") {
1824
+ issues.push(`${entryPath}: parseConfig is required`);
1825
+ }
1826
+ if (typeof entry?.createCore !== "function") {
1827
+ issues.push(`${entryPath}: createCore is required`);
1828
+ }
1829
+ });
1830
+ return issues;
1831
+ };
1832
+ var registerEntries = (entries, source, state) => {
1833
+ for (const entry of entries) {
1834
+ const strategyName = entry.manifest.name;
1835
+ state.strategyManifestsMap.set(strategyName, entry.manifest);
1836
+ state.strategyEntriesMap.set(strategyName, entry);
1837
+ state.strategySourcesMap.set(strategyName, source);
1838
+ materializeStrategyCreator(strategyName, state);
1839
+ }
1840
+ };
1841
+ var materializeStrategyCreator = (strategyName, state) => {
1842
+ if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
1843
+ return;
1844
+ }
1845
+ const entry = state.strategyEntriesMap.get(strategyName);
1846
+ if (!entry) return;
1847
+ state.strategyCreators.set(
1848
+ strategyName,
1849
+ sharedStrategyRegistry.strategyRuntimeFactory({
1850
+ strategyName,
1851
+ defaults: entry.defaults,
1852
+ createCore: entry.createCore,
1853
+ manifest: entry.manifest,
1854
+ detectorKey: entry.detectorKey,
1855
+ detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
1856
+ resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
1857
+ })
1858
+ );
1859
+ };
1860
+ var setStrategyRuntimeFactory = (factory) => {
1861
+ sharedStrategyRegistry.strategyRuntimeFactory = factory;
1862
+ for (const state of registryStateByProjectRoot.values()) {
1863
+ for (const strategyName of state.strategyEntriesMap.keys()) {
1864
+ materializeStrategyCreator(strategyName, state);
1865
+ }
1866
+ }
1867
+ };
1868
+ var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
1869
+ if (typeof importTradejsModule === "function") {
1870
+ return importTradejsModule(moduleName, cwd);
1871
+ }
1872
+ return import(
1873
+ /* webpackIgnore: true */
1874
+ moduleName
1875
+ );
1876
+ };
1877
+ var resolveStrategyModuleEntry = async ({
1878
+ strategyName,
1879
+ moduleName,
1880
+ cwd
1881
+ }) => {
1882
+ const { projectRoot, state } = getStrategyRegistryState(cwd);
1883
+ const source = moduleName.trim();
1884
+ const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
1885
+ const cacheKey = `${resolvedModuleName}\0${strategyName}`;
1886
+ const cached = state.strategyModuleResolutions.get(cacheKey);
1887
+ if (cached) return cached;
1888
+ const resolution = (async () => {
1889
+ const moduleExport = await importStrategyPluginModule(
1890
+ resolvedModuleName,
1891
+ projectRoot
1892
+ );
1893
+ const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
1894
+ if (!pluginDefinition) {
1895
+ throw new Error(`${source}: export { strategyEntries } is missing`);
1896
+ }
1897
+ const issues = validateStrategyEntries(
1898
+ source,
1899
+ pluginDefinition.strategyEntries
1900
+ );
1901
+ if (issues.length > 0) {
1902
+ throw new Error(
1903
+ ["Invalid TradeJS strategy module:", ...issues].join("\n")
1904
+ );
1905
+ }
1906
+ const entry = pluginDefinition.strategyEntries.find(
1907
+ (candidate) => candidate.manifest.name === strategyName
1908
+ );
1909
+ if (!entry) {
1910
+ throw new Error(`${source}: strategy ${strategyName} is missing`);
1911
+ }
1912
+ const factory = sharedStrategyRegistry.strategyRuntimeFactory;
1913
+ if (!factory) {
1914
+ throw new Error("Strategy runtime factory is not configured");
1915
+ }
1916
+ const manifestsByName = new Map(
1917
+ pluginDefinition.strategyEntries.map(
1918
+ (candidate) => [candidate.manifest.name, candidate.manifest]
1919
+ )
1920
+ );
1692
1921
  return {
1693
- source: null,
1694
- available: false,
1695
- interval: null,
1696
- asOfTs: null,
1697
- stale: null,
1698
- exchangesCount: null,
1699
- totalVolumeUsd: null,
1700
- totalVolumeChange24hPct: null,
1701
- binanceVolumeUsd: null,
1702
- binanceVolumeShare: null,
1703
- topExchangeVolumeShare: null,
1704
- liquidityRegime: null
1922
+ entry,
1923
+ creator: factory({
1924
+ strategyName,
1925
+ defaults: entry.defaults,
1926
+ createCore: entry.createCore,
1927
+ manifest: entry.manifest,
1928
+ detectorKey: entry.detectorKey,
1929
+ detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
1930
+ resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
1931
+ }),
1932
+ source
1705
1933
  };
1934
+ })();
1935
+ state.strategyModuleResolutions.set(cacheKey, resolution);
1936
+ try {
1937
+ return await resolution;
1938
+ } catch (error) {
1939
+ state.strategyModuleResolutions.delete(cacheKey);
1940
+ throw error;
1706
1941
  }
1707
- return {
1708
- source: String(cmcExchangeLiquidity.source ?? ""),
1709
- available: true,
1710
- interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
1711
- asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
1712
- stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
1713
- exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
1714
- totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
1715
- totalVolumeChange24hPct: toFiniteNumber(
1716
- cmcExchangeLiquidity.totalVolumeChange24hPct
1717
- ),
1718
- binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
1719
- binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
1720
- topExchangeVolumeShare: toFiniteNumber(
1721
- cmcExchangeLiquidity.topExchangeVolumeShare
1722
- ),
1723
- liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
1724
- };
1725
1942
  };
1726
- var buildCmcFearGreedContextFromSignal = (signal) => {
1727
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1728
- const relative = toRecord(baseContext?.relative);
1729
- const cmcFearGreed = toRecord(relative?.cmcFearGreed);
1730
- if (!cmcFearGreed) {
1731
- return {
1732
- source: null,
1733
- available: false,
1734
- interval: null,
1735
- asOfTs: null,
1736
- stale: null,
1737
- value: null,
1738
- valueChange24h: null,
1739
- valueChange7d: null,
1740
- classification: null,
1741
- sentimentRegime: null
1742
- };
1943
+ var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
1944
+ const { projectRoot, state } = getStrategyRegistryState(cwd);
1945
+ if (!state.pluginsLoadPromise) {
1946
+ (0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
1947
+ state.pluginsLoadPromise = (async () => {
1948
+ const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
1949
+ const strategySet = new Set(strategyModules);
1950
+ const indicatorSet = new Set(indicatorModules);
1951
+ const pluginModuleNames = [
1952
+ .../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
1953
+ ];
1954
+ if (!pluginModuleNames.length) {
1955
+ return;
1956
+ }
1957
+ const issues = [];
1958
+ for (const moduleName of pluginModuleNames) {
1959
+ try {
1960
+ const resolvedModuleName = resolvePluginModuleSpecifier(
1961
+ moduleName,
1962
+ projectRoot
1963
+ );
1964
+ const moduleExport = await importStrategyPluginModule(
1965
+ resolvedModuleName,
1966
+ projectRoot
1967
+ );
1968
+ if (strategySet.has(moduleName)) {
1969
+ const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
1970
+ if (!pluginDefinition) {
1971
+ issues.push(
1972
+ `${moduleName}: export { strategyEntries } is missing`
1973
+ );
1974
+ } else {
1975
+ const entryIssues = validateStrategyEntries(
1976
+ moduleName,
1977
+ pluginDefinition.strategyEntries,
1978
+ state
1979
+ );
1980
+ issues.push(...entryIssues);
1981
+ if (entryIssues.length === 0) {
1982
+ registerEntries(
1983
+ pluginDefinition.strategyEntries,
1984
+ moduleName,
1985
+ state
1986
+ );
1987
+ }
1988
+ }
1989
+ }
1990
+ if (indicatorSet.has(moduleName)) {
1991
+ const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
1992
+ if (!indicatorPluginDefinition) {
1993
+ issues.push(
1994
+ `${moduleName}: export { indicatorEntries } is missing`
1995
+ );
1996
+ } else {
1997
+ (0, import_indicators.registerIndicatorEntries)(
1998
+ indicatorPluginDefinition.indicatorEntries,
1999
+ moduleName,
2000
+ projectRoot
2001
+ );
2002
+ }
2003
+ }
2004
+ if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
2005
+ issues.push(`${moduleName}: plugin is not declared in config`);
2006
+ }
2007
+ } catch (error) {
2008
+ issues.push(`${moduleName}: failed to import: ${String(error)}`);
2009
+ }
2010
+ }
2011
+ if (issues.length > 0) {
2012
+ throw new Error(
2013
+ ["Invalid TradeJS plugin catalog:", ...issues].join("\n")
2014
+ );
2015
+ }
2016
+ })();
1743
2017
  }
1744
- return {
1745
- source: String(cmcFearGreed.source ?? ""),
1746
- available: true,
1747
- interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
1748
- asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
1749
- stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
1750
- value: toFiniteNumber(cmcFearGreed.value),
1751
- valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
1752
- valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
1753
- classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
1754
- sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
1755
- };
2018
+ await state.pluginsLoadPromise;
1756
2019
  };
1757
- var buildCmcIndexesContextFromSignal = (signal) => {
1758
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1759
- const relative = toRecord(baseContext?.relative);
1760
- const cmcIndexes = toRecord(relative?.cmcIndexes);
1761
- if (!cmcIndexes) {
1762
- return {
1763
- source: null,
1764
- available: false,
1765
- interval: null,
1766
- asOfTs: null,
1767
- stale: null,
1768
- cmc100Value: null,
1769
- cmc100Change24hPct: null,
1770
- cmc100TopConstituentSymbol: null,
1771
- cmc100TopConstituentWeightPct: null,
1772
- cmc20Value: null,
1773
- cmc20Change24hPct: null,
1774
- cmc20TopConstituentSymbol: null,
1775
- cmc20TopConstituentWeightPct: null,
1776
- cmc20ToCmc100Ratio: null,
1777
- cmc20ToCmc100RatioChange24hPct: null,
1778
- indexRegime: null
1779
- };
2020
+ var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
2021
+ if (moduleName?.trim()) {
2022
+ await ensureStrategyPluginsLoaded(cwd);
2023
+ return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
1780
2024
  }
1781
- return {
1782
- source: String(cmcIndexes.source ?? ""),
1783
- available: true,
1784
- interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
1785
- asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
1786
- stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
1787
- cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
1788
- cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
1789
- cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
1790
- cmc100TopConstituentWeightPct: toFiniteNumber(
1791
- cmcIndexes.cmc100TopConstituentWeightPct
1792
- ),
1793
- cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
1794
- cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
1795
- cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
1796
- cmc20TopConstituentWeightPct: toFiniteNumber(
1797
- cmcIndexes.cmc20TopConstituentWeightPct
1798
- ),
1799
- cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
1800
- cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
1801
- cmcIndexes.cmc20ToCmc100RatioChange24hPct
1802
- ),
1803
- indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
1804
- };
2025
+ await ensureStrategyPluginsLoaded(cwd);
2026
+ const { state } = getStrategyRegistryState(cwd);
2027
+ return state.strategyCreators.get(name);
1805
2028
  };
1806
- var buildReferenceTradeFlowContextFromSignal = (signal) => {
1807
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
1808
- const relative = toRecord(baseContext?.relative);
1809
- const refs = toRecord(relative?.referenceTradeFlow);
1810
- const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
1811
- const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
1812
- const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
1813
- if (!refs) {
1814
- return {
1815
- source: null,
1816
- available: false,
1817
- primaryReferenceSymbol: null,
1818
- referenceSymbols: [],
1819
- primaryTradeFlowBuyPressurePct: null,
1820
- primaryTradeFlowStale: null
1821
- };
2029
+ var getStrategyEntry = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
2030
+ if (moduleName?.trim()) {
2031
+ await ensureStrategyPluginsLoaded(cwd);
2032
+ return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).entry;
2033
+ }
2034
+ await ensureStrategyPluginsLoaded(cwd);
2035
+ const { state } = getStrategyRegistryState(cwd);
2036
+ return state.strategyEntriesMap.get(name);
2037
+ };
2038
+ var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
2039
+ if (moduleName?.trim()) {
2040
+ await ensureStrategyPluginsLoaded(cwd);
2041
+ return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).source;
2042
+ }
2043
+ await ensureStrategyPluginsLoaded(cwd);
2044
+ const { state } = getStrategyRegistryState(cwd);
2045
+ return state.strategySourcesMap.get(name);
2046
+ };
2047
+ var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
2048
+ if (!name) {
2049
+ return void 0;
1822
2050
  }
1823
- return {
1824
- source: String(refs.source ?? ""),
1825
- available: true,
1826
- primaryReferenceSymbol,
1827
- referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
1828
- primaryTradeFlowBuyPressurePct: toFiniteNumber(
1829
- primaryTradeFlow?.buyPressurePct
1830
- ),
1831
- primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
1832
- };
2051
+ const { state } = getStrategyRegistryState(cwd);
2052
+ return state.strategyManifestsMap.get(name);
1833
2053
  };
1834
- var buildAiMarketContext = (signal) => ({
1835
- execution: {
1836
- binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
1837
- },
1838
- participation: {
1839
- trueDelta: buildTrueDeltaContextFromSignal(signal),
1840
- tradeFlow: buildTradeFlowContextFromSignal(signal)
1841
- },
1842
- relative: {
1843
- marketBreadth: buildMarketBreadthContextFromSignal(signal),
1844
- marketBreadths: buildMarketBreadthsContextFromSignal(signal),
1845
- targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
1846
- btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
1847
- cmcGlobal: buildCmcGlobalContextFromSignal(signal),
1848
- cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
1849
- cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
1850
- cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
1851
- cmcIndexes: buildCmcIndexesContextFromSignal(signal),
1852
- referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
2054
+ var strategies = new Proxy(
2055
+ {},
2056
+ {
2057
+ get: (_target, property) => {
2058
+ if (typeof property !== "string") {
2059
+ return void 0;
2060
+ }
2061
+ return getStrategyRegistryState().state.strategyCreators.get(property);
2062
+ },
2063
+ ownKeys: () => {
2064
+ return [...getStrategyRegistryState().state.strategyCreators.keys()];
2065
+ },
2066
+ getOwnPropertyDescriptor: () => ({
2067
+ enumerable: true,
2068
+ configurable: true
2069
+ })
1853
2070
  }
1854
- });
1855
-
1856
- // src/strategy/manifests.ts
1857
- var import_indicators = require("@tradejs/core/indicators");
2071
+ );
1858
2072
 
1859
- // src/tradejsConfig.ts
1860
- var import_fs = __toESM(require("fs"));
1861
- var import_path = __toESM(require("path"));
1862
- var import_url = require("url");
1863
- var import_config = require("@tradejs/core/config");
1864
- var import_logger2 = require("@tradejs/infra/logger");
1865
- var CONFIG_FILE_NAMES = [
1866
- "tradejs.config.ts",
1867
- "tradejs.config.mts",
1868
- "tradejs.config.js",
1869
- "tradejs.config.mjs",
1870
- "tradejs.config.cjs"
1871
- ];
1872
- var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
1873
- var cachedByCwd = /* @__PURE__ */ new Map();
1874
- var announcedConfigFile = /* @__PURE__ */ new Set();
1875
- var tsNodeRegistered = false;
1876
- var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
1877
- var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
1878
- var getTradejsProjectCwd = (cwd) => {
1879
- const explicit = String(cwd ?? "").trim();
1880
- if (explicit) {
1881
- return import_path.default.resolve(explicit);
2073
+ // src/strategyHelpers/tickerProfiles.ts
2074
+ var snapshots = /* @__PURE__ */ new Map();
2075
+ var resolveTickerProfileSource = (strategy) => {
2076
+ const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
2077
+ if (declared) return declared;
2078
+ const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
2079
+ const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
2080
+ if (!provider && !universeId) return void 0;
2081
+ if (!provider || !universeId)
2082
+ throw new Error(
2083
+ "Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
2084
+ );
2085
+ return { provider, universeId };
2086
+ };
2087
+ var loadTickerProfileContext = async ({
2088
+ projectRoot,
2089
+ provider,
2090
+ universeId,
2091
+ symbol,
2092
+ timestamp
2093
+ }) => {
2094
+ const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
2095
+ let cached = snapshots.get(key);
2096
+ if (!cached || cached.expires < Date.now()) {
2097
+ const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
2098
+ if (!envelope) return null;
2099
+ const s = envelope.snapshot;
2100
+ if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
2101
+ throw new Error("Ticker profile identity/schema mismatch");
2102
+ const entry = snapshots.get(key);
2103
+ if (entry) entry.expires = Infinity;
2104
+ return new Map(
2105
+ s.profiles.map((p) => [
2106
+ p.symbol,
2107
+ Object.freeze({
2108
+ ...p,
2109
+ definition: s.definition,
2110
+ provider,
2111
+ universeId,
2112
+ snapshotId: envelope.snapshotId
2113
+ })
2114
+ ])
2115
+ );
2116
+ }).catch((error) => {
2117
+ snapshots.delete(key);
2118
+ throw error;
2119
+ });
2120
+ cached = { expires: Date.now() + 6e4, value };
2121
+ snapshots.set(key, cached);
2122
+ if (snapshots.size > 24) {
2123
+ const oldest = snapshots.keys().next().value;
2124
+ if (oldest) snapshots.delete(oldest);
2125
+ }
1882
2126
  }
1883
- const fromEnv = String(process.env.PROJECT_CWD || "").trim();
1884
- if (fromEnv) {
1885
- return import_path.default.resolve(fromEnv);
2127
+ return (await cached.value)?.get(symbol);
2128
+ };
2129
+ var withTickerProfile = (baseContext, profile) => Object.defineProperty(
2130
+ Object.create(
2131
+ Object.getPrototypeOf(baseContext),
2132
+ Object.getOwnPropertyDescriptors(baseContext)
2133
+ ),
2134
+ "tickerProfile",
2135
+ { enumerable: true, configurable: true, value: profile }
2136
+ );
2137
+ var enrichSignalWithTickerProfile = async (signal) => {
2138
+ const source = resolveTickerProfileSource(signal.strategy);
2139
+ if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
2140
+ return;
2141
+ const profile = await loadTickerProfileContext({
2142
+ ...source,
2143
+ projectRoot: getTradejsProjectCwd(),
2144
+ symbol: signal.symbol,
2145
+ timestamp: signal.timestamp
2146
+ });
2147
+ if (profile) {
2148
+ signal.additionalIndicators = {
2149
+ ...signal.additionalIndicators,
2150
+ tickerProfile: profile
2151
+ };
1886
2152
  }
1887
- return process.cwd();
1888
2153
  };
1889
- var normalizeConfig = (rawConfig) => {
1890
- if (!rawConfig || typeof rawConfig !== "object") {
1891
- return {};
2154
+
2155
+ // src/strategyRuntime.ts
2156
+ var import_jev6 = require("@tradejs/core/jev");
2157
+
2158
+ // src/jev.ts
2159
+ var import_promises = __toESM(require("fs/promises"));
2160
+ var import_node_path = __toESM(require("path"));
2161
+ var import_jev4 = require("@tradejs/core/jev");
2162
+ var import_jev5 = require("@tradejs/infra/jev");
2163
+ var import_userSettings = require("@tradejs/infra/userSettings");
2164
+ init_jevInput();
2165
+ init_jevInput();
2166
+ init_jevTraining();
2167
+ var resolveJevProvider = async (userName) => {
2168
+ const settings = await (0, import_userSettings.getUserSettings)(userName);
2169
+ const provider = {
2170
+ endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
2171
+ model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
2172
+ };
2173
+ (0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
2174
+ if (!settings.JEV_API_KEY)
2175
+ throw new Error("Configure the Jev API key in Account settings");
2176
+ return provider;
2177
+ };
2178
+ var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
2179
+ var pending = /* @__PURE__ */ new Map();
2180
+ var pendingResponses = /* @__PURE__ */ new Map();
2181
+ var evaluateJevInput = async ({
2182
+ input,
2183
+ config,
2184
+ userName,
2185
+ projectRoot
2186
+ }) => {
2187
+ config = (0, import_jev4.parseJevConfig)(config);
2188
+ const inputHash = (0, import_jev5.jevHash)(input);
2189
+ const questions = questionsForJevInput(input);
2190
+ const questionsHash = (0, import_jev5.jevHash)(questions);
2191
+ const base = {
2192
+ questionSet: import_jev4.JEV_QUESTION_SET,
2193
+ schema: "tradejs-signal-assessment/v5",
2194
+ source: config.source,
2195
+ mode: config.mode,
2196
+ inputHash,
2197
+ questionsHash,
2198
+ geometryStatus: input.geometryStatus,
2199
+ levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
2200
+ };
2201
+ if (!input.questions.length)
2202
+ return {
2203
+ assessment: {
2204
+ ...base,
2205
+ status: "unavailable",
2206
+ model: "",
2207
+ scores: emptyScores(),
2208
+ confidence: emptyScores()
2209
+ }
2210
+ };
2211
+ if (config.source === "local") {
2212
+ const contents = await import_promises.default.readFile(
2213
+ import_node_path.default.resolve(projectRoot, config.modelFile),
2214
+ "utf8"
2215
+ );
2216
+ if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
2217
+ throw new Error("Local Jev gate checksum mismatch");
2218
+ const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
2219
+ const model = validateJevGateModel2(JSON.parse(contents));
2220
+ if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
2221
+ throw new Error("Local Jev gate strategy or question version mismatch");
2222
+ const scores = Object.fromEntries(
2223
+ import_jev4.JEV_DIMENSIONS.map((key) => [
2224
+ key,
2225
+ input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
2226
+ ])
2227
+ );
2228
+ return {
2229
+ assessment: {
2230
+ ...base,
2231
+ status: "available",
2232
+ model: config.modelSha256,
2233
+ scores,
2234
+ confidence: emptyScores()
2235
+ }
2236
+ };
1892
2237
  }
1893
- const config = rawConfig;
1894
- const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
1895
- const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
1896
- const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
1897
- const hooks = (0, import_config.normalizeTradejsConfigHooks)(
1898
- config.hooks
1899
- );
1900
- const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
1901
- return {
1902
- strategies: strategies2,
1903
- indicators,
1904
- connectors,
1905
- ...hooks ? { hooks } : {},
1906
- ...runtime ? { runtime } : {}
2238
+ const provider = config.provider ?? await resolveJevProvider(userName);
2239
+ const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
2240
+ const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
2241
+ const file = import_node_path.default.join(dir, "records", `${id}.json`);
2242
+ const getRecord = async () => {
2243
+ const existing = await (0, import_jev5.readJevArtifact)(file);
2244
+ if (existing) {
2245
+ 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))
2246
+ throw new Error("Jev recording identity mismatch");
2247
+ (0, import_jev5.validateJevResponse)(existing.response, input.questions);
2248
+ for (const key of import_jev4.JEV_DIMENSIONS) {
2249
+ const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
2250
+ if (existing.scores[key] !== expected)
2251
+ throw new Error("Jev recording scores do not match the response");
2252
+ }
2253
+ return existing;
2254
+ }
2255
+ if (config.source === "recorded")
2256
+ throw new Error(
2257
+ `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.`
2258
+ );
2259
+ const settings = await (0, import_userSettings.getUserSettings)(userName);
2260
+ if (!settings.JEV_API_KEY)
2261
+ throw new Error("Configure the Jev API key in Account settings");
2262
+ const apiKey = settings.JEV_API_KEY;
2263
+ const configuredProvider = await resolveJevProvider(userName);
2264
+ if (configuredProvider.endpoint !== provider.endpoint)
2265
+ throw new Error(
2266
+ "Jev account endpoint changed; update the frozen provider configuration"
2267
+ );
2268
+ const started = Date.now();
2269
+ const state = stateForJevInput(input);
2270
+ const responseId = (0, import_jev5.jevHash)({
2271
+ state,
2272
+ questionsHash,
2273
+ provider,
2274
+ user: (0, import_jev5.jevHash)(userName)
2275
+ });
2276
+ const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
2277
+ let responsePromise = pendingResponses.get(responseFile);
2278
+ if (!responsePromise) {
2279
+ responsePromise = (async () => {
2280
+ const saved = await (0, import_jev5.readJevArtifact)(responseFile);
2281
+ if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
2282
+ const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
2283
+ await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
2284
+ return fresh;
2285
+ })();
2286
+ pendingResponses.set(responseFile, responsePromise);
2287
+ }
2288
+ let response;
2289
+ try {
2290
+ response = await responsePromise;
2291
+ } finally {
2292
+ pendingResponses.delete(responseFile);
2293
+ }
2294
+ const scores = Object.fromEntries(
2295
+ import_jev4.JEV_DIMENSIONS.map((key) => [
2296
+ key,
2297
+ input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
2298
+ ])
2299
+ );
2300
+ const value = {
2301
+ schema: "tradejs-jev-record/v4",
2302
+ id,
2303
+ inputHash,
2304
+ questionsHash,
2305
+ provider,
2306
+ input,
2307
+ response,
2308
+ scores,
2309
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2310
+ elapsedMs: Date.now() - started
2311
+ };
2312
+ await (0, import_jev5.writeJevArtifact)(file, value);
2313
+ return await (0, import_jev5.readJevArtifact)(file);
1907
2314
  };
1908
- };
1909
- var getNodeCreateRequire = () => {
1910
- const builtinModule = process.getBuiltinModule?.("module");
1911
- if (typeof builtinModule?.createRequire === "function") {
1912
- return builtinModule.createRequire;
2315
+ const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
2316
+ let promise = pending.get(pendingKey);
2317
+ if (!promise) {
2318
+ promise = getRecord();
2319
+ pending.set(pendingKey, promise);
1913
2320
  }
1914
- throw new TypeError("module.createRequire is not available");
1915
- };
1916
- var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
1917
- var ensureTsNodeRegistered = async () => {
1918
- if (tsNodeRegistered) {
1919
- return;
2321
+ let record2;
2322
+ try {
2323
+ record2 = await promise;
2324
+ } finally {
2325
+ pending.delete(pendingKey);
1920
2326
  }
1921
- const tsNodeModule = await import("ts-node");
1922
- const tsNode = tsNodeModule.default ?? tsNodeModule;
1923
- tsNode.register?.({
1924
- transpileOnly: true,
1925
- compilerOptions: {
1926
- module: "Node16",
1927
- moduleResolution: "node16"
2327
+ return {
2328
+ record: record2,
2329
+ assessment: {
2330
+ ...base,
2331
+ status: "available",
2332
+ recordId: id,
2333
+ model: record2.response.model,
2334
+ scores: record2.scores,
2335
+ confidence: Object.fromEntries(
2336
+ import_jev4.JEV_DIMENSIONS.map((key) => [
2337
+ key,
2338
+ record2.response.answers[key]?.confidence ?? null
2339
+ ])
2340
+ )
1928
2341
  }
1929
- });
1930
- tsNodeRegistered = true;
1931
- };
1932
- var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
1933
- const projectRoot = getTradejsProjectCwd(cwd);
1934
- if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
1935
- return;
1936
- }
1937
- const tsconfigPathsModule = await import("tsconfig-paths");
1938
- const loadConfig = tsconfigPathsModule.loadConfig;
1939
- const register = tsconfigPathsModule.register;
1940
- if (typeof loadConfig !== "function" || typeof register !== "function") {
1941
- return;
1942
- }
1943
- const loadedConfig = loadConfig(projectRoot);
1944
- if (loadedConfig.resultType !== "success") {
1945
- return;
1946
- }
1947
- register({
1948
- baseUrl: loadedConfig.absoluteBaseUrl,
1949
- paths: loadedConfig.paths,
1950
- addMatchAll: false
1951
- });
1952
- tsconfigPathsRegisteredByCwd.add(projectRoot);
2342
+ };
1953
2343
  };
1954
- var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
1955
- const projectRoot = getTradejsProjectCwd(cwd);
1956
- const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
1957
- if (cachedMatcher) {
1958
- const resolved2 = cachedMatcher(moduleName);
1959
- return resolved2 || null;
1960
- }
1961
- const tsconfigPathsModule = await import("tsconfig-paths");
1962
- const loadConfig = tsconfigPathsModule.loadConfig;
1963
- const createMatchPath = tsconfigPathsModule.createMatchPath;
1964
- if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
1965
- return null;
1966
- }
1967
- const loadedConfig = loadConfig(projectRoot);
1968
- if (loadedConfig.resultType !== "success") {
1969
- return null;
2344
+ var assessSignalWithJev = async ({
2345
+ signal,
2346
+ config,
2347
+ userName,
2348
+ projectRoot,
2349
+ strict,
2350
+ scope = "runtime"
2351
+ }) => {
2352
+ let assessment;
2353
+ try {
2354
+ const input = buildJevInput(signal);
2355
+ ({ assessment } = await evaluateJevInput({
2356
+ input,
2357
+ config,
2358
+ userName,
2359
+ projectRoot
2360
+ }));
2361
+ } catch (error) {
2362
+ if (strict) throw error;
2363
+ assessment = {
2364
+ questionSet: import_jev4.JEV_QUESTION_SET,
2365
+ schema: "tradejs-signal-assessment/v5",
2366
+ source: config.source,
2367
+ mode: config.mode,
2368
+ status: "unavailable",
2369
+ inputHash: "",
2370
+ questionsHash: "",
2371
+ geometryStatus: "absent",
2372
+ levelsValid: null,
2373
+ model: config.provider?.model ?? config.modelSha256 ?? "",
2374
+ scores: emptyScores(),
2375
+ confidence: emptyScores()
2376
+ };
1970
2377
  }
1971
- const matchPath = createMatchPath(
1972
- loadedConfig.absoluteBaseUrl,
1973
- loadedConfig.paths
2378
+ signal.assessment = assessment;
2379
+ signal.additionalIndicators = {
2380
+ ...signal.additionalIndicators,
2381
+ jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
2382
+ };
2383
+ const identity = {
2384
+ signalId: signal.signalId,
2385
+ strategy: signal.strategy,
2386
+ symbol: signal.symbol,
2387
+ timestamp: signal.timestamp,
2388
+ scope,
2389
+ user: (0, import_jev5.jevHash)(userName),
2390
+ config,
2391
+ assessment
2392
+ };
2393
+ await (0, import_jev5.writeJevArtifact)(
2394
+ import_node_path.default.resolve(
2395
+ projectRoot,
2396
+ config.recordsDir ?? "data/ai/jev",
2397
+ "decisions",
2398
+ `${(0, import_jev5.jevHash)(identity)}.json`
2399
+ ),
2400
+ identity
1974
2401
  );
1975
- const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
1976
- ".ts",
1977
- ".tsx",
1978
- ".mts",
1979
- ".cts",
1980
- ".js",
1981
- ".jsx",
1982
- ".mjs",
1983
- ".cjs",
1984
- ".json"
1985
- ]) || "";
1986
- tsconfigPathMatchersByCwd.set(projectRoot, matcher);
1987
- const resolved = matcher(moduleName);
1988
- return resolved || null;
2402
+ return assessment;
1989
2403
  };
1990
- var toImportSpecifier = (moduleName) => {
1991
- if (moduleName.startsWith("file://")) {
1992
- return moduleName;
1993
- }
1994
- if (import_path.default.isAbsolute(moduleName)) {
1995
- return (0, import_url.pathToFileURL)(moduleName).href;
2404
+
2405
+ // src/strategyRuntime.ts
2406
+ var import_constants5 = require("@tradejs/core/constants");
2407
+ var import_strategies6 = require("@tradejs/core/strategies");
2408
+ var import_logger11 = require("@tradejs/infra/logger");
2409
+
2410
+ // src/strategyHelpers/runtime.ts
2411
+ var import_logger9 = require("@tradejs/infra/logger");
2412
+ var import_constants3 = require("@tradejs/core/constants");
2413
+ var import_ml2 = require("@tradejs/infra/ml");
2414
+
2415
+ // src/strategy/entrySubmissionValidation.ts
2416
+ var import_logger3 = require("@tradejs/infra/logger");
2417
+
2418
+ // src/strategy/entryExecutionValidation.ts
2419
+ var EntrySubmissionValidationError = class extends Error {
2420
+ constructor() {
2421
+ super(...arguments);
2422
+ this.name = "EntrySubmissionValidationError";
1996
2423
  }
1997
- return moduleName;
1998
2424
  };
1999
- var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
2000
- var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
2001
- var isBareModuleSpecifier = (moduleName) => {
2002
- const normalized = String(moduleName ?? "").trim();
2003
- if (!normalized) {
2004
- return false;
2005
- }
2006
- if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
2007
- return false;
2008
- }
2009
- return true;
2425
+ var resolveEntryExecutionLimits = (config, direction) => {
2426
+ const side = config[direction];
2427
+ const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
2428
+ const maximum = config.MAX_LOSS_VALUE;
2429
+ if (minimum == null && maximum == null) return void 0;
2430
+ const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
2431
+ const maxLossValue = maximum == null ? void 0 : Number(maximum);
2432
+ const feeRate = Number(config.RISK_FEE_RATE ?? 0);
2433
+ const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
2434
+ const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
2435
+ const slippageBps = slippage + impact;
2436
+ 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)
2437
+ throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
2438
+ return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
2010
2439
  };
2011
- var importConfigFile = async (configFilePath) => {
2012
- const ext = import_path.default.extname(configFilePath).toLowerCase();
2013
- const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
2014
- if (ext === ".ts" || ext === ".mts") {
2015
- const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
2016
- await ensureTsNodeRegistered();
2017
- await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
2018
- return requireFn(configFilePath);
2019
- }
2020
- return import(
2021
- /* webpackIgnore: true */
2022
- configFileUrl
2440
+ var validateEntryExecution = ({
2441
+ direction,
2442
+ price,
2443
+ qty,
2444
+ stopLossPrice,
2445
+ takeProfitPrice,
2446
+ limits
2447
+ }) => {
2448
+ if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
2449
+ throw new EntrySubmissionValidationError(
2450
+ "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
2451
+ );
2452
+ const side = direction === "LONG" ? 1 : -1;
2453
+ if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
2454
+ throw new EntrySubmissionValidationError(
2455
+ "INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
2456
+ );
2457
+ const slippage = limits.slippageBps / 1e4;
2458
+ const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
2459
+ const entryCost = cost(price, side);
2460
+ const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
2461
+ const rewardPerUnit = Math.max(
2462
+ 0,
2463
+ Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
2023
2464
  );
2465
+ const netRiskRatio = rewardPerUnit / lossPerUnit;
2466
+ const plannedLossValue = qty * lossPerUnit;
2467
+ if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
2468
+ throw new EntrySubmissionValidationError(
2469
+ "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
2470
+ );
2471
+ const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
2472
+ const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
2473
+ return {
2474
+ netRiskRatio,
2475
+ plannedLossValue,
2476
+ riskExceeded,
2477
+ belowMinimumNetRiskRatio
2478
+ };
2024
2479
  };
2025
- var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
2026
- const normalized = String(moduleName ?? "").trim();
2027
- if (!normalized) {
2028
- return {};
2029
- }
2030
- let modulePath = normalized;
2031
- if (normalized.startsWith("file://")) {
2032
- try {
2033
- modulePath = (0, import_url.fileURLToPath)(normalized);
2034
- } catch {
2035
- modulePath = normalized;
2036
- }
2037
- }
2038
- const requireFn = getRequireFn(
2039
- import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
2040
- );
2041
- if (isTsModulePath(modulePath)) {
2042
- await ensureTsNodeRegistered();
2043
- await ensureTsconfigPathsRegistered(cwd);
2044
- return requireFn(modulePath);
2045
- }
2046
- if (isBareModuleSpecifier(normalized)) {
2047
- await ensureTsconfigPathsRegistered(cwd);
2048
- try {
2049
- return requireFn(normalized);
2050
- } catch (error) {
2051
- const resolvedByTsconfig = await resolveTsconfigPathModule(
2052
- normalized,
2053
- cwd
2054
- );
2055
- if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
2056
- return requireFn(resolvedByTsconfig);
2057
- }
2058
- throw error;
2059
- }
2480
+
2481
+ // src/strategy/entrySubmissionValidation.ts
2482
+ var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
2483
+ var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
2484
+ var getOrderArrivalSnapshot = async ({
2485
+ connector,
2486
+ symbol,
2487
+ requireTimestamp = false
2488
+ }) => {
2489
+ if (typeof connector.getTopOfBookTicker !== "function") {
2490
+ return {
2491
+ arrivalSnapshotTime: Date.now(),
2492
+ arrivalSource: "unavailable",
2493
+ bid: null,
2494
+ ask: null,
2495
+ arrivalMid: null,
2496
+ spreadBps: null
2497
+ };
2060
2498
  }
2061
2499
  try {
2062
- return await import(
2063
- /* webpackIgnore: true */
2064
- toImportSpecifier(normalized)
2065
- );
2500
+ const ticker = await connector.getTopOfBookTicker(symbol);
2501
+ const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
2502
+ const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
2503
+ const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
2504
+ const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
2505
+ const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
2506
+ const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
2507
+ return {
2508
+ arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
2509
+ arrivalSource: "top_of_book",
2510
+ bid,
2511
+ ask,
2512
+ arrivalMid,
2513
+ spreadBps
2514
+ };
2066
2515
  } catch (error) {
2067
- if (isTsModulePath(modulePath)) {
2068
- await ensureTsNodeRegistered();
2069
- await ensureTsconfigPathsRegistered(cwd);
2070
- return requireFn(modulePath);
2071
- }
2072
- throw error;
2073
- }
2074
- };
2075
- var resolveExportedConfig = (moduleExports) => {
2076
- const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
2077
- return normalizeConfig(candidate);
2078
- };
2079
- var findConfigFilePath = (cwd) => {
2080
- let currentDir = import_path.default.resolve(cwd);
2081
- while (true) {
2082
- for (const fileName of CONFIG_FILE_NAMES) {
2083
- const fullPath = import_path.default.join(currentDir, fileName);
2084
- if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
2085
- return fullPath;
2086
- }
2087
- }
2088
- const parentDir = import_path.default.dirname(currentDir);
2089
- if (parentDir === currentDir) {
2090
- return null;
2091
- }
2092
- currentDir = parentDir;
2516
+ import_logger3.logger.warn(
2517
+ "runtime order arrival snapshot failed: %s %s",
2518
+ symbol,
2519
+ error?.message || String(error)
2520
+ );
2521
+ return {
2522
+ arrivalSnapshotTime: Date.now(),
2523
+ arrivalSource: "top_of_book_error",
2524
+ bid: null,
2525
+ ask: null,
2526
+ arrivalMid: null,
2527
+ spreadBps: null
2528
+ };
2093
2529
  }
2094
2530
  };
2095
- var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
2096
- const normalized = String(moduleName ?? "").trim();
2097
- if (!normalized) {
2098
- return "";
2531
+ var validateEntryProtectionAtArrival = ({
2532
+ direction,
2533
+ signalPrice,
2534
+ bid,
2535
+ ask,
2536
+ arrivalMid,
2537
+ takeProfits,
2538
+ stopLossPrice
2539
+ }) => {
2540
+ const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
2541
+ const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
2542
+ if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
2543
+ throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
2099
2544
  }
2100
- if (normalized.startsWith("file://")) {
2101
- try {
2102
- return (0, import_url.fileURLToPath)(normalized);
2103
- } catch {
2104
- return normalized;
2545
+ for (const takeProfit of takeProfits) {
2546
+ if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
2547
+ throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
2105
2548
  }
2106
2549
  }
2107
- if (import_path.default.isAbsolute(normalized)) {
2108
- return normalized;
2109
- }
2110
- if (isRelativeModulePath(normalized)) {
2111
- return import_path.default.resolve(cwd, normalized);
2550
+ if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
2551
+ throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
2112
2552
  }
2113
- return normalized;
2114
2553
  };
2115
- var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
2116
- const cached = cachedByCwd.get(cwd);
2117
- if (cached) {
2118
- return cached;
2119
- }
2120
- const configFilePath = findConfigFilePath(cwd);
2121
- if (!configFilePath) {
2122
- cachedByCwd.set(cwd, {});
2123
- return {};
2124
- }
2125
- try {
2126
- const moduleExports = await importConfigFile(configFilePath);
2127
- const config = resolveExportedConfig(moduleExports);
2128
- cachedByCwd.set(cwd, config);
2129
- if (!announcedConfigFile.has(configFilePath)) {
2130
- announcedConfigFile.add(configFilePath);
2131
- import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
2554
+ var createEntrySubmissionValidation = ({
2555
+ connector,
2556
+ symbol,
2557
+ direction,
2558
+ takeProfits,
2559
+ stopLossPrice,
2560
+ limits,
2561
+ onValidated
2562
+ }) => {
2563
+ if (!limits) return void 0;
2564
+ if (!connector.capabilities?.orderSubmissionValidation)
2565
+ throw new EntrySubmissionValidationError(
2566
+ "ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
2567
+ );
2568
+ return async ({ qty, executionPrice }) => {
2569
+ const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
2570
+ const snapshot2 = simulated && executionPrice != null ? {
2571
+ arrivalSnapshotTime: Date.now(),
2572
+ arrivalSource: "simulation",
2573
+ bid: executionPrice,
2574
+ ask: executionPrice,
2575
+ arrivalMid: executionPrice,
2576
+ spreadBps: 0
2577
+ } : await getOrderArrivalSnapshot({
2578
+ connector,
2579
+ symbol,
2580
+ requireTimestamp: true
2581
+ });
2582
+ const age = Date.now() - snapshot2.arrivalSnapshotTime;
2583
+ if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
2584
+ throw new EntrySubmissionValidationError(
2585
+ "ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
2586
+ );
2587
+ if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
2588
+ throw new EntrySubmissionValidationError(
2589
+ "ENTRY_QUOTE_STALE_AT_SUBMISSION"
2590
+ );
2591
+ const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
2592
+ validateEntryProtectionAtArrival({
2593
+ direction,
2594
+ signalPrice: price,
2595
+ bid: snapshot2.bid,
2596
+ ask: snapshot2.ask,
2597
+ arrivalMid: snapshot2.arrivalMid,
2598
+ takeProfits,
2599
+ stopLossPrice
2600
+ });
2601
+ const prices = takeProfits.map((tp) => tp.price);
2602
+ const economics2 = validateEntryExecution({
2603
+ direction,
2604
+ price,
2605
+ qty,
2606
+ stopLossPrice,
2607
+ takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
2608
+ limits
2609
+ });
2610
+ if (economics2.belowMinimumNetRiskRatio) {
2611
+ import_logger3.logger.warn(
2612
+ "entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
2613
+ symbol,
2614
+ direction,
2615
+ economics2.netRiskRatio,
2616
+ limits.minimumNetRiskRatio
2617
+ );
2618
+ }
2619
+ onValidated?.(snapshot2, economics2);
2620
+ };
2621
+ };
2622
+
2623
+ // src/ai.ts
2624
+ var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
2625
+ var import_redis3 = require("@tradejs/infra/redis");
2626
+
2627
+ // src/strategyAdapters/ai.ts
2628
+ var import_indicators2 = require("@tradejs/core/indicators");
2629
+ var import_aiFeatures = require("@tradejs/core/aiFeatures");
2630
+
2631
+ // src/aiShared.ts
2632
+ var MAX_AI_SERIES_POINTS = 5;
2633
+ var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
2634
+ "tradejs.indicators.compactSnapshot"
2635
+ );
2636
+ var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
2637
+ var trimSeriesDeep = (value) => {
2638
+ if (Array.isArray(value)) {
2639
+ const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
2640
+ const isMatrix = trimmed.every((item) => Array.isArray(item));
2641
+ if (isMatrix) {
2642
+ return trimmed;
2132
2643
  }
2133
- return config;
2134
- } catch (error) {
2135
- import_logger2.logger.log(
2136
- "warn",
2137
- "Failed to load TradeJS config from %s: %s",
2138
- configFilePath,
2139
- String(error)
2644
+ return trimmed.map(
2645
+ (item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
2140
2646
  );
2141
- cachedByCwd.set(cwd, {});
2142
- return {};
2143
2647
  }
2648
+ if (value && typeof value === "object") {
2649
+ return Object.fromEntries(
2650
+ Object.entries(value).map(([key, nested]) => [
2651
+ key,
2652
+ trimSeriesDeep(nested)
2653
+ ])
2654
+ );
2655
+ }
2656
+ return value;
2657
+ };
2658
+ var buildCompactAiIndicatorsSnapshot = (value) => {
2659
+ const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
2660
+ if (typeof compactSnapshot === "function") {
2661
+ return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
2662
+ }
2663
+ return trimSeriesDeep(value);
2144
2664
  };
2145
2665
 
2146
- // src/strategy/manifests.ts
2147
- var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
2148
- var sharedRegistryScope = globalThis;
2149
- var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
2150
- registryStateByProjectRoot: /* @__PURE__ */ new Map()
2151
- });
2152
- var createStrategyRegistryState = () => ({
2153
- strategyCreators: /* @__PURE__ */ new Map(),
2154
- strategyManifestsMap: /* @__PURE__ */ new Map(),
2155
- strategyEntriesMap: /* @__PURE__ */ new Map(),
2156
- strategySourcesMap: /* @__PURE__ */ new Map(),
2157
- strategyModuleResolutions: /* @__PURE__ */ new Map(),
2158
- pluginsLoadPromise: null
2666
+ // src/aiMarketContext.ts
2667
+ var toRecord = (value) => {
2668
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2669
+ return null;
2670
+ }
2671
+ return value;
2672
+ };
2673
+ var toFiniteNumber = (value) => {
2674
+ const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
2675
+ return Number.isFinite(numeric) ? numeric : null;
2676
+ };
2677
+ var roundTo = (value, decimals) => {
2678
+ const factor = 10 ** decimals;
2679
+ return Math.round(value * factor) / factor;
2680
+ };
2681
+ var buildMissingSpreadContext = () => ({
2682
+ source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
2683
+ indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
2684
+ available: false,
2685
+ value: null,
2686
+ zScore: null,
2687
+ bps: null,
2688
+ absBps: null,
2689
+ bias: null,
2690
+ severity: null
2159
2691
  });
2160
- var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
2161
- var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
2162
- const projectRoot = getTradejsProjectCwd(cwd);
2163
- let state = registryStateByProjectRoot.get(projectRoot);
2164
- if (!state) {
2165
- state = createStrategyRegistryState();
2166
- registryStateByProjectRoot.set(projectRoot, state);
2692
+ var buildSpreadContextFromSignal = (signal) => {
2693
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
2694
+ const relative = toRecord(baseContext?.relative);
2695
+ const execution = toRecord(relative?.execution);
2696
+ const spread = toFiniteNumber(execution?.venueSpread);
2697
+ const zScore = toFiniteNumber(execution?.venueSpreadZScore);
2698
+ if (spread == null) {
2699
+ return buildMissingSpreadContext();
2167
2700
  }
2701
+ const value = roundTo(spread, 8);
2702
+ const bps = roundTo(value * 1e4, 2);
2703
+ const absBps = Math.abs(bps);
2704
+ const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
2705
+ const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
2168
2706
  return {
2169
- projectRoot,
2170
- state
2707
+ source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
2708
+ indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
2709
+ available: true,
2710
+ value,
2711
+ zScore,
2712
+ bps,
2713
+ absBps,
2714
+ bias,
2715
+ severity
2171
2716
  };
2172
2717
  };
2173
- var toUniqueModules = (modules = []) => [
2174
- ...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
2175
- ];
2176
- var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
2177
- const config = await loadTradejsConfig(cwd);
2718
+ var buildTrueDeltaContextFromSignal = (signal) => {
2719
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
2720
+ const participation = toRecord(baseContext?.participation);
2721
+ const delta = toRecord(participation?.delta);
2722
+ const source = String(delta?.source ?? "");
2723
+ const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
2724
+ if (!delta || !isTrueDeltaSource) {
2725
+ return {
2726
+ source: source || null,
2727
+ available: false,
2728
+ buyPressurePct: null,
2729
+ buyVolume: null,
2730
+ sellVolume: null,
2731
+ netDelta: null,
2732
+ deltaPct: null,
2733
+ signedVolumeZScore: null
2734
+ };
2735
+ }
2178
2736
  return {
2179
- strategyModules: toUniqueModules(config.strategies),
2180
- indicatorModules: toUniqueModules(config.indicators)
2737
+ source,
2738
+ available: true,
2739
+ buyPressurePct: toFiniteNumber(delta.buyPressurePct),
2740
+ buyVolume: toFiniteNumber(delta.buyVolume),
2741
+ sellVolume: toFiniteNumber(delta.sellVolume),
2742
+ netDelta: toFiniteNumber(delta.netDelta),
2743
+ deltaPct: toFiniteNumber(delta.deltaPct),
2744
+ signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
2181
2745
  };
2182
2746
  };
2183
- var extractModuleEntries = (moduleExport, key) => {
2184
- if (!moduleExport || typeof moduleExport !== "object") {
2185
- return null;
2186
- }
2187
- const candidate = moduleExport;
2188
- if (Array.isArray(candidate[key])) {
2189
- return candidate[key];
2190
- }
2191
- const defaultExport = candidate.default;
2192
- if (defaultExport && Array.isArray(defaultExport[key])) {
2193
- return defaultExport[key];
2747
+ var buildTradeFlowContextFromSignal = (signal) => {
2748
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
2749
+ const participation = toRecord(baseContext?.participation);
2750
+ const tradeFlow = toRecord(participation?.tradeFlow);
2751
+ if (!tradeFlow) {
2752
+ return {
2753
+ source: null,
2754
+ available: false,
2755
+ interval: null,
2756
+ stale: null,
2757
+ trades: null,
2758
+ buyPressurePct: null,
2759
+ netBaseDelta: null,
2760
+ netQuoteDelta: null
2761
+ };
2194
2762
  }
2195
- return null;
2196
- };
2197
- var extractStrategyPluginDefinition = (moduleExport) => {
2198
- const strategyEntries = extractModuleEntries(
2199
- moduleExport,
2200
- "strategyEntries"
2201
- );
2202
- return strategyEntries ? { strategyEntries } : null;
2763
+ return {
2764
+ source: String(tradeFlow.source ?? ""),
2765
+ available: true,
2766
+ interval: String(tradeFlow.interval ?? ""),
2767
+ stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
2768
+ trades: toFiniteNumber(tradeFlow.trades),
2769
+ buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
2770
+ netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
2771
+ netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
2772
+ };
2203
2773
  };
2204
- var extractIndicatorPluginDefinition = (moduleExport) => {
2205
- const indicatorEntries = extractModuleEntries(
2206
- moduleExport,
2207
- "indicatorEntries"
2208
- );
2209
- return indicatorEntries ? { indicatorEntries } : null;
2774
+ var buildMarketBreadthContextFromSignal = (signal) => {
2775
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
2776
+ const relative = toRecord(baseContext?.relative);
2777
+ const breadth = toRecord(relative?.marketBreadth);
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
+ };
2793
+ }
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
+ };
2210
2808
  };
2211
- var validateStrategyEntries = (moduleName, entries, state) => {
2212
- const issues = [];
2213
- const names = /* @__PURE__ */ new Set();
2214
- entries.forEach((entry, index) => {
2215
- const entryPath = `${moduleName}.strategyEntries[${index}]`;
2216
- const strategyName = entry?.manifest?.name;
2217
- if (typeof strategyName !== "string" || !strategyName.trim()) {
2218
- issues.push(`${entryPath}: manifest.name is required`);
2219
- } else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
2220
- issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
2221
- } else {
2222
- names.add(strategyName);
2223
- }
2224
- if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
2225
- issues.push(`${entryPath}: defaults must be an object`);
2226
- }
2227
- if (typeof entry?.parseConfig !== "function") {
2228
- issues.push(`${entryPath}: parseConfig is required`);
2229
- }
2230
- if (typeof entry?.createCore !== "function") {
2231
- issues.push(`${entryPath}: createCore is required`);
2809
+ var buildMarketBreadthsContextFromSignal = (signal) => {
2810
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
2811
+ const relative = toRecord(baseContext?.relative);
2812
+ const breadths = toRecord(relative?.marketBreadths);
2813
+ const build = (key) => {
2814
+ const breadth = toRecord(breadths?.[key]);
2815
+ if (!breadth) {
2816
+ return {
2817
+ source: null,
2818
+ available: false,
2819
+ universe: null,
2820
+ interval: null,
2821
+ stale: null,
2822
+ symbolsCount: null,
2823
+ advanceDeclineRatio: null,
2824
+ pctAboveMa20: null,
2825
+ pctAboveMa50: null,
2826
+ equalWeightedReturn: null,
2827
+ volumeWeightedReturn: null,
2828
+ dispersion: null
2829
+ };
2232
2830
  }
2233
- });
2234
- return issues;
2235
- };
2236
- var registerEntries = (entries, source, state) => {
2237
- for (const entry of entries) {
2238
- const strategyName = entry.manifest.name;
2239
- state.strategyManifestsMap.set(strategyName, entry.manifest);
2240
- state.strategyEntriesMap.set(strategyName, entry);
2241
- state.strategySourcesMap.set(strategyName, source);
2242
- materializeStrategyCreator(strategyName, state);
2243
- }
2831
+ return {
2832
+ source: String(breadth.source ?? ""),
2833
+ available: true,
2834
+ universe: String(breadth.universe ?? ""),
2835
+ interval: String(breadth.interval ?? ""),
2836
+ stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
2837
+ symbolsCount: toFiniteNumber(breadth.symbolsCount),
2838
+ advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
2839
+ pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
2840
+ pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
2841
+ equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
2842
+ volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
2843
+ dispersion: toFiniteNumber(breadth.dispersion)
2844
+ };
2845
+ };
2846
+ return {
2847
+ top5: build("top5"),
2848
+ top10: build("top10"),
2849
+ top30: build("top30"),
2850
+ top50: build("top50"),
2851
+ top100: build("top100")
2852
+ };
2244
2853
  };
2245
- var materializeStrategyCreator = (strategyName, state) => {
2246
- if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
2247
- return;
2248
- }
2249
- const entry = state.strategyEntriesMap.get(strategyName);
2250
- if (!entry) return;
2251
- state.strategyCreators.set(
2252
- strategyName,
2253
- sharedStrategyRegistry.strategyRuntimeFactory({
2254
- strategyName,
2255
- defaults: entry.defaults,
2256
- createCore: entry.createCore,
2257
- manifest: entry.manifest,
2258
- detectorKey: entry.detectorKey,
2259
- detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
2260
- resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
2261
- })
2262
- );
2854
+ var buildTargetVsReference = (signal, asset) => {
2855
+ const base = toRecord(signal.additionalIndicators?.baseContext);
2856
+ const relative = toRecord(base?.relative);
2857
+ const target = toRecord(relative?.[`targetVs${asset}`]);
2858
+ return {
2859
+ source: target ? String(target.source ?? "") : null,
2860
+ available: Boolean(target),
2861
+ ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
2862
+ ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
2863
+ ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
2864
+ alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
2865
+ alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
2866
+ alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
2867
+ beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
2868
+ correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
2869
+ ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
2870
+ };
2263
2871
  };
2264
- var setStrategyRuntimeFactory = (factory) => {
2265
- sharedStrategyRegistry.strategyRuntimeFactory = factory;
2266
- for (const state of registryStateByProjectRoot.values()) {
2267
- for (const strategyName of state.strategyEntriesMap.keys()) {
2268
- materializeStrategyCreator(strategyName, state);
2269
- }
2270
- }
2872
+ var buildTargetVsBtcContextFromSignal = (signal) => {
2873
+ const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
2874
+ return {
2875
+ ...shared,
2876
+ alphaVsBtc1h: alpha1h,
2877
+ alphaVsBtc4h: alpha4h,
2878
+ alphaVsBtc24h: alpha24h,
2879
+ betaToBtc20: beta,
2880
+ correlationToBtc20: correlation
2881
+ };
2271
2882
  };
2272
- var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
2273
- if (typeof importTradejsModule === "function") {
2274
- return importTradejsModule(moduleName, cwd);
2275
- }
2276
- return import(
2277
- /* webpackIgnore: true */
2278
- moduleName
2279
- );
2883
+ var buildTargetVsEthContextFromSignal = (signal) => {
2884
+ const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
2885
+ return {
2886
+ ...shared,
2887
+ alphaVsEth1h: alpha1h,
2888
+ alphaVsEth4h: alpha4h,
2889
+ alphaVsEth24h: alpha24h,
2890
+ betaToEth20: beta,
2891
+ correlationToEth20: correlation
2892
+ };
2280
2893
  };
2281
- var resolveStrategyModuleEntry = async ({
2282
- strategyName,
2283
- moduleName,
2284
- cwd
2285
- }) => {
2286
- const { projectRoot, state } = getStrategyRegistryState(cwd);
2287
- const source = moduleName.trim();
2288
- const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
2289
- const cacheKey = `${resolvedModuleName}\0${strategyName}`;
2290
- const cached = state.strategyModuleResolutions.get(cacheKey);
2291
- if (cached) return cached;
2292
- const resolution = (async () => {
2293
- const moduleExport = await importStrategyPluginModule(
2294
- resolvedModuleName,
2295
- projectRoot
2296
- );
2297
- const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
2298
- if (!pluginDefinition) {
2299
- throw new Error(`${source}: export { strategyEntries } is missing`);
2300
- }
2301
- const issues = validateStrategyEntries(
2302
- source,
2303
- pluginDefinition.strategyEntries
2304
- );
2305
- if (issues.length > 0) {
2306
- throw new Error(
2307
- ["Invalid TradeJS strategy module:", ...issues].join("\n")
2308
- );
2309
- }
2310
- const entry = pluginDefinition.strategyEntries.find(
2311
- (candidate) => candidate.manifest.name === strategyName
2312
- );
2313
- if (!entry) {
2314
- throw new Error(`${source}: strategy ${strategyName} is missing`);
2315
- }
2316
- const factory = sharedStrategyRegistry.strategyRuntimeFactory;
2317
- if (!factory) {
2318
- throw new Error("Strategy runtime factory is not configured");
2319
- }
2320
- const manifestsByName = new Map(
2321
- pluginDefinition.strategyEntries.map(
2322
- (candidate) => [candidate.manifest.name, candidate.manifest]
2323
- )
2324
- );
2894
+ var buildBtcAltRegimeContextFromSignal = (signal) => {
2895
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
2896
+ const relative = toRecord(baseContext?.relative);
2897
+ const btcAltRegime = toRecord(relative?.btcAltRegime);
2898
+ if (!btcAltRegime) {
2325
2899
  return {
2326
- entry,
2327
- creator: factory({
2328
- strategyName,
2329
- defaults: entry.defaults,
2330
- createCore: entry.createCore,
2331
- manifest: entry.manifest,
2332
- detectorKey: entry.detectorKey,
2333
- detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
2334
- resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
2335
- }),
2336
- source
2900
+ source: null,
2901
+ available: false,
2902
+ universe: null,
2903
+ interval: null,
2904
+ stale: null,
2905
+ regime: null,
2906
+ btcReturn24h: null,
2907
+ altBasketReturn24h: null,
2908
+ btcVsAltReturn24h: null,
2909
+ btcTurnoverShare24h: null,
2910
+ btcTurnoverShareChange24h: null,
2911
+ altVolToBtcVol24h: null,
2912
+ altDispersion24h: null
2337
2913
  };
2338
- })();
2339
- state.strategyModuleResolutions.set(cacheKey, resolution);
2340
- try {
2341
- return await resolution;
2342
- } catch (error) {
2343
- state.strategyModuleResolutions.delete(cacheKey);
2344
- throw error;
2345
2914
  }
2915
+ return {
2916
+ source: String(btcAltRegime.source ?? ""),
2917
+ available: true,
2918
+ universe: String(btcAltRegime.universe ?? ""),
2919
+ interval: String(btcAltRegime.interval ?? ""),
2920
+ stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
2921
+ regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
2922
+ btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
2923
+ altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
2924
+ btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
2925
+ btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
2926
+ btcTurnoverShareChange24h: toFiniteNumber(
2927
+ btcAltRegime.btcTurnoverShareChange24h
2928
+ ),
2929
+ altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
2930
+ altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
2931
+ };
2346
2932
  };
2347
- var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
2348
- const { projectRoot, state } = getStrategyRegistryState(cwd);
2349
- if (!state.pluginsLoadPromise) {
2350
- (0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
2351
- state.pluginsLoadPromise = (async () => {
2352
- const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
2353
- const strategySet = new Set(strategyModules);
2354
- const indicatorSet = new Set(indicatorModules);
2355
- const pluginModuleNames = [
2356
- .../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
2357
- ];
2358
- if (!pluginModuleNames.length) {
2359
- return;
2360
- }
2361
- const issues = [];
2362
- for (const moduleName of pluginModuleNames) {
2363
- try {
2364
- const resolvedModuleName = resolvePluginModuleSpecifier(
2365
- moduleName,
2366
- projectRoot
2367
- );
2368
- const moduleExport = await importStrategyPluginModule(
2369
- resolvedModuleName,
2370
- projectRoot
2371
- );
2372
- if (strategySet.has(moduleName)) {
2373
- const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
2374
- if (!pluginDefinition) {
2375
- issues.push(
2376
- `${moduleName}: export { strategyEntries } is missing`
2377
- );
2378
- } else {
2379
- const entryIssues = validateStrategyEntries(
2380
- moduleName,
2381
- pluginDefinition.strategyEntries,
2382
- state
2383
- );
2384
- issues.push(...entryIssues);
2385
- if (entryIssues.length === 0) {
2386
- registerEntries(
2387
- pluginDefinition.strategyEntries,
2388
- moduleName,
2389
- state
2390
- );
2391
- }
2392
- }
2393
- }
2394
- if (indicatorSet.has(moduleName)) {
2395
- const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
2396
- if (!indicatorPluginDefinition) {
2397
- issues.push(
2398
- `${moduleName}: export { indicatorEntries } is missing`
2399
- );
2400
- } else {
2401
- (0, import_indicators.registerIndicatorEntries)(
2402
- indicatorPluginDefinition.indicatorEntries,
2403
- moduleName,
2404
- projectRoot
2405
- );
2406
- }
2407
- }
2408
- if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
2409
- issues.push(`${moduleName}: plugin is not declared in config`);
2410
- }
2411
- } catch (error) {
2412
- issues.push(`${moduleName}: failed to import: ${String(error)}`);
2413
- }
2414
- }
2415
- if (issues.length > 0) {
2416
- throw new Error(
2417
- ["Invalid TradeJS plugin catalog:", ...issues].join("\n")
2418
- );
2419
- }
2420
- })();
2933
+ var buildCmcGlobalContextFromSignal = (signal) => {
2934
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
2935
+ const relative = toRecord(baseContext?.relative);
2936
+ const cmcGlobal = toRecord(relative?.cmcGlobal);
2937
+ if (!cmcGlobal) {
2938
+ return {
2939
+ source: null,
2940
+ available: false,
2941
+ interval: null,
2942
+ asOfTs: null,
2943
+ stale: null,
2944
+ totalMarketCapUsd: null,
2945
+ totalVolumeUsd: null,
2946
+ totalVolumeReportedUsd: null,
2947
+ altMarketCapUsd: null,
2948
+ altVolumeUsd: null,
2949
+ altVolumeReportedUsd: null,
2950
+ btcDominancePct: null,
2951
+ ethDominancePct: null,
2952
+ btcDominanceChange24hPct: null,
2953
+ ethDominanceChange24hPct: null,
2954
+ altMarketCapChange24hPct: null,
2955
+ altVolumeChange24hPct: null,
2956
+ activeCryptocurrencies: null,
2957
+ activeExchanges: null,
2958
+ activeMarketPairs: null,
2959
+ altLiquidityRegime: null
2960
+ };
2421
2961
  }
2422
- await state.pluginsLoadPromise;
2962
+ return {
2963
+ source: String(cmcGlobal.source ?? ""),
2964
+ available: true,
2965
+ interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
2966
+ asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
2967
+ stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
2968
+ totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
2969
+ totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
2970
+ totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
2971
+ altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
2972
+ altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
2973
+ altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
2974
+ btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
2975
+ ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
2976
+ btcDominanceChange24hPct: toFiniteNumber(
2977
+ cmcGlobal.btcDominanceChange24hPct
2978
+ ),
2979
+ ethDominanceChange24hPct: toFiniteNumber(
2980
+ cmcGlobal.ethDominanceChange24hPct
2981
+ ),
2982
+ altMarketCapChange24hPct: toFiniteNumber(
2983
+ cmcGlobal.altMarketCapChange24hPct
2984
+ ),
2985
+ altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
2986
+ activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
2987
+ activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
2988
+ activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
2989
+ altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
2990
+ };
2423
2991
  };
2424
- var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
2425
- if (moduleName?.trim()) {
2426
- await ensureStrategyPluginsLoaded(cwd);
2427
- return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
2992
+ var buildCmcReferenceAssetsContextFromSignal = (signal) => {
2993
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
2994
+ const relative = toRecord(baseContext?.relative);
2995
+ const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
2996
+ if (!cmcReferenceAssets) {
2997
+ return {
2998
+ source: null,
2999
+ available: false,
3000
+ interval: null,
3001
+ asOfTs: null,
3002
+ stale: null,
3003
+ btcMarketCapUsd: null,
3004
+ ethMarketCapUsd: null,
3005
+ btcVolumeUsd: null,
3006
+ ethVolumeUsd: null,
3007
+ btcVolumeToMarketCap: null,
3008
+ ethVolumeToMarketCap: null,
3009
+ ethBtcMarketCapRatio: null,
3010
+ ethBtcMarketCapRatioChange24hPct: null,
3011
+ ethVsBtcVolumeRatio: null,
3012
+ referenceLiquidityRegime: null
3013
+ };
2428
3014
  }
2429
- await ensureStrategyPluginsLoaded(cwd);
2430
- const { state } = getStrategyRegistryState(cwd);
2431
- return state.strategyCreators.get(name);
3015
+ return {
3016
+ source: String(cmcReferenceAssets.source ?? ""),
3017
+ available: true,
3018
+ interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
3019
+ asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
3020
+ stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
3021
+ btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
3022
+ ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
3023
+ btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
3024
+ ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
3025
+ btcVolumeToMarketCap: toFiniteNumber(
3026
+ cmcReferenceAssets.btcVolumeToMarketCap
3027
+ ),
3028
+ ethVolumeToMarketCap: toFiniteNumber(
3029
+ cmcReferenceAssets.ethVolumeToMarketCap
3030
+ ),
3031
+ ethBtcMarketCapRatio: toFiniteNumber(
3032
+ cmcReferenceAssets.ethBtcMarketCapRatio
3033
+ ),
3034
+ ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
3035
+ cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
3036
+ ),
3037
+ ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
3038
+ referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
3039
+ };
2432
3040
  };
2433
- var getStrategyEntry = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
2434
- if (moduleName?.trim()) {
2435
- await ensureStrategyPluginsLoaded(cwd);
2436
- return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).entry;
3041
+ var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
3042
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
3043
+ const relative = toRecord(baseContext?.relative);
3044
+ const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
3045
+ if (!cmcExchangeLiquidity) {
3046
+ return {
3047
+ source: null,
3048
+ available: false,
3049
+ interval: null,
3050
+ asOfTs: null,
3051
+ stale: null,
3052
+ exchangesCount: null,
3053
+ totalVolumeUsd: null,
3054
+ totalVolumeChange24hPct: null,
3055
+ binanceVolumeUsd: null,
3056
+ binanceVolumeShare: null,
3057
+ topExchangeVolumeShare: null,
3058
+ liquidityRegime: null
3059
+ };
2437
3060
  }
2438
- await ensureStrategyPluginsLoaded(cwd);
2439
- const { state } = getStrategyRegistryState(cwd);
2440
- return state.strategyEntriesMap.get(name);
3061
+ return {
3062
+ source: String(cmcExchangeLiquidity.source ?? ""),
3063
+ available: true,
3064
+ interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
3065
+ asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
3066
+ stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
3067
+ exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
3068
+ totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
3069
+ totalVolumeChange24hPct: toFiniteNumber(
3070
+ cmcExchangeLiquidity.totalVolumeChange24hPct
3071
+ ),
3072
+ binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
3073
+ binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
3074
+ topExchangeVolumeShare: toFiniteNumber(
3075
+ cmcExchangeLiquidity.topExchangeVolumeShare
3076
+ ),
3077
+ liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
3078
+ };
2441
3079
  };
2442
- var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
2443
- if (moduleName?.trim()) {
2444
- await ensureStrategyPluginsLoaded(cwd);
2445
- return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).source;
3080
+ var buildCmcFearGreedContextFromSignal = (signal) => {
3081
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
3082
+ const relative = toRecord(baseContext?.relative);
3083
+ const cmcFearGreed = toRecord(relative?.cmcFearGreed);
3084
+ if (!cmcFearGreed) {
3085
+ return {
3086
+ source: null,
3087
+ available: false,
3088
+ interval: null,
3089
+ asOfTs: null,
3090
+ stale: null,
3091
+ value: null,
3092
+ valueChange24h: null,
3093
+ valueChange7d: null,
3094
+ classification: null,
3095
+ sentimentRegime: null
3096
+ };
2446
3097
  }
2447
- await ensureStrategyPluginsLoaded(cwd);
2448
- const { state } = getStrategyRegistryState(cwd);
2449
- return state.strategySourcesMap.get(name);
3098
+ return {
3099
+ source: String(cmcFearGreed.source ?? ""),
3100
+ available: true,
3101
+ interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
3102
+ asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
3103
+ stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
3104
+ value: toFiniteNumber(cmcFearGreed.value),
3105
+ valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
3106
+ valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
3107
+ classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
3108
+ sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
3109
+ };
2450
3110
  };
2451
- var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
2452
- if (!name) {
2453
- return void 0;
3111
+ var buildCmcIndexesContextFromSignal = (signal) => {
3112
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
3113
+ const relative = toRecord(baseContext?.relative);
3114
+ const cmcIndexes = toRecord(relative?.cmcIndexes);
3115
+ if (!cmcIndexes) {
3116
+ return {
3117
+ source: null,
3118
+ available: false,
3119
+ interval: null,
3120
+ asOfTs: null,
3121
+ stale: null,
3122
+ cmc100Value: null,
3123
+ cmc100Change24hPct: null,
3124
+ cmc100TopConstituentSymbol: null,
3125
+ cmc100TopConstituentWeightPct: null,
3126
+ cmc20Value: null,
3127
+ cmc20Change24hPct: null,
3128
+ cmc20TopConstituentSymbol: null,
3129
+ cmc20TopConstituentWeightPct: null,
3130
+ cmc20ToCmc100Ratio: null,
3131
+ cmc20ToCmc100RatioChange24hPct: null,
3132
+ indexRegime: null
3133
+ };
2454
3134
  }
2455
- const { state } = getStrategyRegistryState(cwd);
2456
- return state.strategyManifestsMap.get(name);
3135
+ return {
3136
+ source: String(cmcIndexes.source ?? ""),
3137
+ available: true,
3138
+ interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
3139
+ asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
3140
+ stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
3141
+ cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
3142
+ cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
3143
+ cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
3144
+ cmc100TopConstituentWeightPct: toFiniteNumber(
3145
+ cmcIndexes.cmc100TopConstituentWeightPct
3146
+ ),
3147
+ cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
3148
+ cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
3149
+ cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
3150
+ cmc20TopConstituentWeightPct: toFiniteNumber(
3151
+ cmcIndexes.cmc20TopConstituentWeightPct
3152
+ ),
3153
+ cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
3154
+ cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
3155
+ cmcIndexes.cmc20ToCmc100RatioChange24hPct
3156
+ ),
3157
+ indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
3158
+ };
3159
+ };
3160
+ var buildReferenceTradeFlowContextFromSignal = (signal) => {
3161
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
3162
+ const relative = toRecord(baseContext?.relative);
3163
+ const refs = toRecord(relative?.referenceTradeFlow);
3164
+ const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
3165
+ const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
3166
+ const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
3167
+ if (!refs) {
3168
+ return {
3169
+ source: null,
3170
+ available: false,
3171
+ primaryReferenceSymbol: null,
3172
+ referenceSymbols: [],
3173
+ primaryTradeFlowBuyPressurePct: null,
3174
+ primaryTradeFlowStale: null
3175
+ };
3176
+ }
3177
+ return {
3178
+ source: String(refs.source ?? ""),
3179
+ available: true,
3180
+ primaryReferenceSymbol,
3181
+ referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
3182
+ primaryTradeFlowBuyPressurePct: toFiniteNumber(
3183
+ primaryTradeFlow?.buyPressurePct
3184
+ ),
3185
+ primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
3186
+ };
2457
3187
  };
2458
- var strategies = new Proxy(
2459
- {},
2460
- {
2461
- get: (_target, property) => {
2462
- if (typeof property !== "string") {
2463
- return void 0;
2464
- }
2465
- return getStrategyRegistryState().state.strategyCreators.get(property);
2466
- },
2467
- ownKeys: () => {
2468
- return [...getStrategyRegistryState().state.strategyCreators.keys()];
2469
- },
2470
- getOwnPropertyDescriptor: () => ({
2471
- enumerable: true,
2472
- configurable: true
2473
- })
3188
+ var buildAiMarketContext = (signal) => ({
3189
+ execution: {
3190
+ binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
3191
+ },
3192
+ participation: {
3193
+ trueDelta: buildTrueDeltaContextFromSignal(signal),
3194
+ tradeFlow: buildTradeFlowContextFromSignal(signal)
3195
+ },
3196
+ relative: {
3197
+ marketBreadth: buildMarketBreadthContextFromSignal(signal),
3198
+ marketBreadths: buildMarketBreadthsContextFromSignal(signal),
3199
+ targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
3200
+ targetVsEth: buildTargetVsEthContextFromSignal(signal),
3201
+ btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
3202
+ cmcGlobal: buildCmcGlobalContextFromSignal(signal),
3203
+ cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
3204
+ cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
3205
+ cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
3206
+ cmcIndexes: buildCmcIndexesContextFromSignal(signal),
3207
+ referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
2474
3208
  }
2475
- );
3209
+ });
2476
3210
 
2477
3211
  // src/strategy/policyProfiles.ts
2478
3212
  var profileMatches = (profile, universe, assetClass) => {
@@ -2574,12 +3308,61 @@ var buildBaseAiPayload = (signal) => {
2574
3308
  };
2575
3309
  };
2576
3310
  var defaultAiAdapter = {};
3311
+ var tickerProfileFeatureIds = new Set(
3312
+ import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
3313
+ );
2577
3314
  var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
2578
3315
  var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
2579
3316
  var buildAiPayloadByStrategy = (signal) => {
2580
3317
  const basePayload = buildBaseAiPayload(signal);
2581
3318
  const adapter = getSignalAiAdapter(signal);
2582
- return adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
3319
+ const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
3320
+ const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
3321
+ const frozen = signal.aiFeatureReplay?.snapshot;
3322
+ if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
3323
+ const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
3324
+ const hasProfile = Boolean(
3325
+ tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
3326
+ );
3327
+ const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
3328
+ getTradejsProjectCwd()
3329
+ ).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
3330
+ const hasPortfolio = Boolean(
3331
+ signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
3332
+ ({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
3333
+ )
3334
+ );
3335
+ const definitions = [
3336
+ ...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
3337
+ ...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
3338
+ ...indicatorFeatures,
3339
+ ...custom
3340
+ ];
3341
+ if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
3342
+ (0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
3343
+ if (signal.aiFeatureReplay && !frozen) {
3344
+ throw new Error(
3345
+ "Dataset has no frozen AI features; regenerate the decision-time export"
3346
+ );
3347
+ }
3348
+ if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
3349
+ throw new Error(
3350
+ "AI feature definition changed; regenerate the decision-time export"
3351
+ );
3352
+ }
3353
+ return {
3354
+ ...payload,
3355
+ featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
3356
+ signal: tickerProfile ? {
3357
+ ...signal,
3358
+ additionalIndicators: {
3359
+ ...signal.additionalIndicators,
3360
+ tickerProfile
3361
+ }
3362
+ } : signal,
3363
+ payload
3364
+ })
3365
+ };
2583
3366
  };
2584
3367
  var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
2585
3368
  var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
@@ -2988,357 +3771,109 @@ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
2988
3771
  const response = await invokeAiPromptChat({
2989
3772
  userName: options.userName,
2990
3773
  model: options.model,
2991
- messages: [
2992
- { role: "system", content: systemPrompt },
2993
- {
2994
- role: "system",
2995
- content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
2996
- },
2997
- { role: "user", content: humanPrompt }
2998
- ]
2999
- });
3000
- const parsed = parseAIResponse(response.content);
3001
- const normalized = normalizeAnalysis(parsed);
3002
- if (!options.signal) {
3003
- return normalized;
3004
- }
3005
- return postProcessAiAnalysisByStrategy(
3006
- options.signal,
3007
- normalized,
3008
- options.payload
3009
- );
3010
- };
3011
- var runAiPromptLocal = async (signal, options = {}) => {
3012
- const payload = options.payload ?? buildAiPayload(signal);
3013
- const gateContext = getDeterministicAiGateContext(payload);
3014
- const signalDirection = getSignalDirection(signal);
3015
- const deterministicQuality = getDeterministicQuality(gateContext);
3016
- const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
3017
- return postProcessLocalAiAnalysisByStrategy(
3018
- signal,
3019
- {
3020
- direction: approvalAllowedNow ? signalDirection : null,
3021
- quality: deterministicQuality,
3022
- needRetest: !approvalAllowedNow,
3023
- retestPrice: null,
3024
- takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
3025
- stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
3026
- },
3027
- payload
3028
- );
3029
- };
3030
- var askAI = async (signal, options = {}) => {
3031
- const { symbol } = signal;
3032
- const payload = buildAiPayload(signal);
3033
- const content = await runAiPrompt(
3034
- {
3035
- systemPrompt: buildAiSystemPrompt(signal),
3036
- humanPrompt: buildAiHumanPrompt(signal, payload)
3037
- },
3038
- {
3039
- ...options,
3040
- signal,
3041
- payload
3042
- }
3043
- );
3044
- await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
3045
- return content;
3046
- };
3047
-
3048
- // src/strategyAdapters/ml.ts
3049
- var defaultMlAdapter = {
3050
- normalizeStrategyConfig: (strategyConfig) => strategyConfig
3051
- };
3052
- var getStrategyMlAdapter = (strategy, profileId) => {
3053
- const strategyAdapter = getStrategyProfileMlAdapter(
3054
- getStrategyManifest(strategy),
3055
- profileId
3056
- );
3057
- if (!strategyAdapter) return defaultMlAdapter;
3058
- return {
3059
- ...defaultMlAdapter,
3060
- ...strategyAdapter
3061
- };
3062
- };
3063
-
3064
- // src/mlPayload.ts
3065
- var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
3066
- return getStrategyMlAdapter(
3067
- strategyName,
3068
- profileId
3069
- ).normalizeStrategyConfig?.(strategyConfig);
3070
- };
3071
- var buildMlPayload = (payload) => {
3072
- const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
3073
- const profileId = payload.signal?.policyProfileId;
3074
- const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
3075
- const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
3076
- const nextSignal = {
3077
- ...normalizedSignal,
3078
- indicators: {
3079
- ...normalizedSignal?.indicators ?? {}
3080
- }
3081
- };
3082
- const nextContext = payload.context ? {
3083
- ...payload.context,
3084
- strategyConfig: normalizeStrategyConfig(
3085
- payload.context.strategyConfig,
3086
- strategyName,
3087
- profileId
3088
- )
3089
- } : void 0;
3090
- return {
3091
- signal: nextSignal,
3092
- context: nextContext
3093
- };
3094
- };
3095
-
3096
- // src/runtimeJournal.ts
3097
- var import_node_crypto = require("crypto");
3098
- var import_constants = require("@tradejs/core/constants");
3099
- var import_time = require("@tradejs/core/time");
3100
- var import_trade = require("@tradejs/core/trade");
3101
- var import_logger3 = require("@tradejs/infra/logger");
3102
- var import_redis2 = require("@tradejs/infra/redis");
3103
- var now = () => Date.now();
3104
- var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
3105
- var calculateClosedPnl = ({
3106
- direction,
3107
- entryPrice,
3108
- exitPrice,
3109
- qty
3110
- }) => {
3111
- const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
3112
- return Number.isFinite(pnl) ? pnl : null;
3113
- };
3114
- var createRuntimeOrderId = (strategy) => {
3115
- const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
3116
- if (prefix === "tjs-") {
3117
- return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
3118
- }
3119
- return `${prefix}${toRandomOrderSuffix()}`;
3120
- };
3121
- var recordRuntimeTradeOpen = async (params) => {
3122
- const { userName } = params;
3123
- if (!userName) {
3124
- return null;
3125
- }
3126
- const record2 = {
3127
- ...params,
3128
- entryCount: 1,
3129
- lastEntryPrice: params.entryPrice,
3130
- lastEntryQty: params.qty,
3131
- lastEntryTimestamp: params.entryTimestamp,
3132
- status: "active",
3133
- currentPrice: params.entryPrice,
3134
- currentPnl: 0,
3135
- closedPnl: null,
3136
- exitPrice: null,
3137
- exitTimestamp: null,
3138
- lastSyncedAt: now()
3139
- };
3140
- const dayKey = (0, import_time.getRuntimeStorageDayKey)(record2.entryTimestamp);
3141
- const runtimeScopeId = record2.deploymentId ?? record2.accountId;
3142
- try {
3143
- await Promise.all([
3144
- (0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record2.orderId), record2, {
3145
- expire: 0
3146
- }),
3147
- (0, import_redis2.setHashJsonField)(
3148
- import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
3149
- record2.orderId,
3150
- record2,
3151
- { expire: 0 }
3152
- ),
3153
- (0, import_redis2.setData)(
3154
- import_redis2.redisKeys.runtimeActiveTrade(userName, record2.symbol, runtimeScopeId),
3155
- { orderId: record2.orderId },
3156
- { expire: 0 }
3157
- )
3158
- ]);
3159
- } catch (error) {
3160
- import_logger3.logger.error(
3161
- "runtime trade open journal failed: %s %s",
3162
- record2.symbol,
3163
- error?.message || String(error)
3164
- );
3165
- }
3166
- return record2;
3167
- };
3168
- var recordRuntimeTradeIncrease = async (params) => {
3169
- const {
3170
- userName,
3171
- strategy,
3172
- symbol,
3173
- direction,
3174
- resultingQty,
3175
- resultingEntryPrice,
3176
- addedQty,
3177
- addedEntryPrice,
3178
- entryTimestamp,
3179
- fee,
3180
- accountId,
3181
- deploymentId
3182
- } = params;
3183
- if (!userName) {
3184
- return null;
3185
- }
3186
- const existing = await getActiveRuntimeTrade({
3187
- userName,
3188
- symbol,
3189
- accountId,
3190
- deploymentId
3774
+ messages: [
3775
+ { role: "system", content: systemPrompt },
3776
+ {
3777
+ role: "system",
3778
+ content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
3779
+ },
3780
+ { role: "user", content: humanPrompt }
3781
+ ]
3191
3782
  });
3192
- if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
3193
- return null;
3194
- }
3195
- const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
3196
- const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
3197
- const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
3198
- const next = {
3199
- ...existing,
3200
- ...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
3201
- qty: resultingQty,
3202
- entryPrice: resultingEntryPrice,
3203
- entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
3204
- lastEntryPrice: addedEntryPrice,
3205
- lastEntryQty: addedQty,
3206
- lastEntryTimestamp: entryTimestamp,
3207
- currentPrice: resultingEntryPrice,
3208
- currentPnl: 0,
3209
- fee: openFee,
3210
- openFee,
3211
- totalFee,
3212
- lastSyncedAt: now()
3213
- };
3214
- const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
3215
- try {
3216
- await Promise.all([
3217
- (0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
3218
- expire: 0
3219
- }),
3220
- (0, import_redis2.setHashJsonField)(
3221
- import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
3222
- existing.orderId,
3223
- next,
3224
- { expire: 0 }
3225
- )
3226
- ]);
3227
- } catch (error) {
3228
- import_logger3.logger.error(
3229
- "runtime trade increase journal failed: %s %s",
3230
- symbol,
3231
- error?.message || String(error)
3232
- );
3783
+ const parsed = parseAIResponse(response.content);
3784
+ const normalized = normalizeAnalysis(parsed);
3785
+ if (!options.signal) {
3786
+ return normalized;
3233
3787
  }
3234
- return next;
3788
+ return postProcessAiAnalysisByStrategy(
3789
+ options.signal,
3790
+ normalized,
3791
+ options.payload
3792
+ );
3235
3793
  };
3236
- var getActiveRuntimeTrade = async (params) => {
3237
- const { userName, symbol, accountId, deploymentId } = params;
3238
- if (!userName) {
3239
- return null;
3240
- }
3241
- const activeRef = await (0, import_redis2.getData)(
3242
- import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
3243
- null
3794
+ var runAiPromptLocal = async (signal, options = {}) => {
3795
+ const payload = options.payload ?? buildAiPayload(signal);
3796
+ const gateContext = getDeterministicAiGateContext(payload);
3797
+ const signalDirection = getSignalDirection(signal);
3798
+ const deterministicQuality = getDeterministicQuality(gateContext);
3799
+ const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
3800
+ return postProcessLocalAiAnalysisByStrategy(
3801
+ signal,
3802
+ {
3803
+ direction: approvalAllowedNow ? signalDirection : null,
3804
+ quality: deterministicQuality,
3805
+ needRetest: !approvalAllowedNow,
3806
+ retestPrice: null,
3807
+ takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
3808
+ stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
3809
+ },
3810
+ payload
3244
3811
  );
3245
- const orderId = String(activeRef?.orderId || "").trim();
3246
- if (!orderId) {
3247
- return null;
3248
- }
3249
- const existing = await (0, import_redis2.getData)(
3250
- import_redis2.redisKeys.runtimeTrade(userName, orderId),
3251
- null
3812
+ };
3813
+ var askAI = async (signal, options = {}) => {
3814
+ const { symbol } = signal;
3815
+ const payload = buildAiPayload(signal);
3816
+ const content = await runAiPrompt(
3817
+ {
3818
+ systemPrompt: buildAiSystemPrompt(signal),
3819
+ humanPrompt: buildAiHumanPrompt(signal, payload)
3820
+ },
3821
+ {
3822
+ ...options,
3823
+ signal,
3824
+ payload
3825
+ }
3252
3826
  );
3253
- if (!existing || existing.status !== "active") {
3254
- await (0, import_redis2.delKey)(
3255
- import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
3256
- );
3257
- return null;
3258
- }
3259
- return existing;
3827
+ await (0, import_redis3.setData)(import_redis3.redisKeys.analysis(symbol, signal.signalId), content);
3828
+ return content;
3260
3829
  };
3261
- var markRuntimeTradeClosed = async (params) => {
3262
- const {
3263
- userName,
3264
- symbol,
3265
- strategy,
3266
- exitPrice,
3267
- exitTimestamp,
3268
- closedPnl,
3269
- exitType,
3270
- accountId,
3271
- deploymentId
3272
- } = params;
3273
- if (!userName) {
3274
- return null;
3275
- }
3276
- const existing = await getActiveRuntimeTrade({
3277
- userName,
3278
- symbol,
3279
- accountId,
3280
- deploymentId
3281
- });
3282
- if (!existing) {
3283
- return null;
3284
- }
3285
- const orderId = existing.orderId;
3286
- if (strategy && existing.strategy !== strategy) {
3287
- return null;
3288
- }
3289
- const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
3290
- const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
3291
- direction: existing.direction,
3292
- entryPrice: existing.entryPrice,
3293
- exitPrice: resolvedExitPrice,
3294
- qty: existing.qty
3295
- }) : existing.closedPnl ?? existing.currentPnl ?? null;
3296
- const next = {
3297
- ...existing,
3298
- status: "closed",
3299
- currentPrice: resolvedExitPrice,
3300
- currentPnl: resolvedClosedPnl,
3301
- closedPnl: resolvedClosedPnl,
3302
- exitPrice: resolvedExitPrice,
3303
- exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
3304
- exitType: exitType ?? existing.exitType ?? null,
3305
- lastSyncedAt: now()
3830
+
3831
+ // src/strategyAdapters/ml.ts
3832
+ var defaultMlAdapter = {
3833
+ normalizeStrategyConfig: (strategyConfig) => strategyConfig
3834
+ };
3835
+ var getStrategyMlAdapter = (strategy, profileId) => {
3836
+ const strategyAdapter = getStrategyProfileMlAdapter(
3837
+ getStrategyManifest(strategy),
3838
+ profileId
3839
+ );
3840
+ if (!strategyAdapter) return defaultMlAdapter;
3841
+ return {
3842
+ ...defaultMlAdapter,
3843
+ ...strategyAdapter
3844
+ };
3845
+ };
3846
+
3847
+ // src/mlPayload.ts
3848
+ var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
3849
+ return getStrategyMlAdapter(
3850
+ strategyName,
3851
+ profileId
3852
+ ).normalizeStrategyConfig?.(strategyConfig);
3853
+ };
3854
+ var buildMlPayload = (payload) => {
3855
+ const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
3856
+ const profileId = payload.signal?.policyProfileId;
3857
+ const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
3858
+ const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
3859
+ const nextSignal = {
3860
+ ...normalizedSignal,
3861
+ indicators: {
3862
+ ...normalizedSignal?.indicators ?? {}
3863
+ }
3864
+ };
3865
+ const nextContext = payload.context ? {
3866
+ ...payload.context,
3867
+ strategyConfig: normalizeStrategyConfig(
3868
+ payload.context.strategyConfig,
3869
+ strategyName,
3870
+ profileId
3871
+ )
3872
+ } : void 0;
3873
+ return {
3874
+ signal: nextSignal,
3875
+ context: nextContext
3306
3876
  };
3307
- const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
3308
- const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
3309
- try {
3310
- await Promise.all([
3311
- (0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
3312
- expire: import_constants.TTL_1M
3313
- }),
3314
- (0, import_redis2.setHashJsonField)(
3315
- import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
3316
- orderId,
3317
- next,
3318
- { expire: import_constants.TTL_1M }
3319
- ),
3320
- (0, import_redis2.setHashJsonField)(
3321
- import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
3322
- orderId,
3323
- next,
3324
- { expire: import_constants.TTL_1M }
3325
- ),
3326
- (0, import_redis2.delKey)(
3327
- import_redis2.redisKeys.runtimeActiveTrade(
3328
- userName,
3329
- symbol,
3330
- deploymentId ?? accountId
3331
- )
3332
- )
3333
- ]);
3334
- } catch (error) {
3335
- import_logger3.logger.error(
3336
- "runtime trade close journal failed: %s %s",
3337
- symbol,
3338
- error?.message || String(error)
3339
- );
3340
- }
3341
- return next;
3342
3877
  };
3343
3878
 
3344
3879
  // src/strategyHelpers/marketContextStages.ts
@@ -3350,7 +3885,7 @@ var import_logger4 = require("@tradejs/infra/logger");
3350
3885
  var import_strategies = require("@tradejs/core/strategies");
3351
3886
 
3352
3887
  // src/binanceBreadthUniverses.ts
3353
- var import_node_crypto2 = require("crypto");
3888
+ var import_node_crypto3 = require("crypto");
3354
3889
 
3355
3890
  // src/config/binanceBreadthUniverses.json
3356
3891
  var binanceBreadthUniverses_default = {
@@ -3589,7 +4124,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
3589
4124
  "top50",
3590
4125
  "top100"
3591
4126
  ];
3592
- var fingerprint = (value, length) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
4127
+ var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
3593
4128
  var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
3594
4129
  var buildBinanceBreadthUniverseSnapshot = ({
3595
4130
  rankedSymbols,
@@ -4613,7 +5148,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4613
5148
  };
4614
5149
 
4615
5150
  // src/strategyHelpers/derivativesContext.ts
4616
- var import_indicators2 = require("@tradejs/core/indicators");
5151
+ var import_indicators3 = require("@tradejs/core/indicators");
4617
5152
  var import_data2 = require("@tradejs/core/data");
4618
5153
  var import_strategies3 = require("@tradejs/core/strategies");
4619
5154
  var import_constants2 = require("@tradejs/core/constants");
@@ -4647,7 +5182,7 @@ var parseLookbackMs = () => {
4647
5182
  };
4648
5183
  var withHourlyFallbackRows = (rowsByInterval) => ({
4649
5184
  "15m": rowsByInterval["15m"] ?? [],
4650
- "1h": (0, import_indicators2.buildCoinalyzeHourlyRowsWithFallback)({
5185
+ "1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
4651
5186
  rows15m: rowsByInterval["15m"],
4652
5187
  fallbackRows1h: rowsByInterval["1h"]
4653
5188
  })
@@ -4775,7 +5310,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4775
5310
  const targetSymbol = normalizeSymbol(signal.symbol);
4776
5311
  const lookbackMs = parseLookbackMs();
4777
5312
  const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
4778
- const derivativesEndMs = (0, import_indicators2.getLastClosedDerivativesBarStartMs)(
5313
+ const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
4779
5314
  decisionTimeMs,
4780
5315
  "15m"
4781
5316
  );
@@ -4790,7 +5325,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4790
5325
  });
4791
5326
  return [
4792
5327
  symbol,
4793
- (0, import_indicators2.buildDerivativesContext)({
5328
+ (0, import_indicators3.buildDerivativesContext)({
4794
5329
  symbol,
4795
5330
  direction: signal.direction,
4796
5331
  timestamp: derivativesEndMs,
@@ -4817,7 +5352,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4817
5352
  lookbackMs,
4818
5353
  ...params.abortSignal ? { signal: params.abortSignal } : {}
4819
5354
  });
4820
- const context = (0, import_indicators2.buildDerivativesContext)({
5355
+ const context = (0, import_indicators3.buildDerivativesContext)({
4821
5356
  symbol: targetSymbol,
4822
5357
  direction: signal.direction,
4823
5358
  timestamp: derivativesEndMs,
@@ -4865,7 +5400,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
4865
5400
  var import_logger7 = require("@tradejs/infra/logger");
4866
5401
 
4867
5402
  // src/hyperliquidWhaleUniverse.ts
4868
- var import_node_crypto3 = require("crypto");
5403
+ var import_node_crypto4 = require("crypto");
4869
5404
 
4870
5405
  // src/config/hyperliquidPerpUniverse.json
4871
5406
  var hyperliquidPerpUniverse_default = {
@@ -5037,7 +5572,7 @@ var hyperliquidWhales_default = {
5037
5572
  };
5038
5573
 
5039
5574
  // src/hyperliquidWhaleUniverse.ts
5040
- var fingerprint2 = (value) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
5575
+ var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
5041
5576
  var validateUnique = (values, label) => {
5042
5577
  if (new Set(values).size !== values.length) {
5043
5578
  throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
@@ -5524,6 +6059,7 @@ var enrichSignalWithMarketContextStages = async ({
5524
6059
  })
5525
6060
  });
5526
6061
  }
6062
+ await enrichSignalWithTickerProfile(signal);
5527
6063
  const results = [];
5528
6064
  for (const stage of stages) {
5529
6065
  if (abortSignal?.aborted) break;
@@ -5571,12 +6107,12 @@ var findReplayAiAnalysis = ({
5571
6107
  direction,
5572
6108
  ai
5573
6109
  }) => {
5574
- const snapshots = ai?.replayAnalyses;
5575
- if (!Array.isArray(snapshots) || !snapshots.length) {
6110
+ const snapshots2 = ai?.replayAnalyses;
6111
+ if (!Array.isArray(snapshots2) || !snapshots2.length) {
5576
6112
  return void 0;
5577
6113
  }
5578
6114
  let best = null;
5579
- for (const snapshot2 of snapshots) {
6115
+ for (const snapshot2 of snapshots2) {
5580
6116
  if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
5581
6117
  continue;
5582
6118
  }
@@ -5736,7 +6272,9 @@ var executeEntryOrder = async ({
5736
6272
  beforePlaceOrder,
5737
6273
  recordRuntimeTrade = true,
5738
6274
  leverage,
5739
- executionLimits
6275
+ executionLimits,
6276
+ markPortfolioSubmitted,
6277
+ rejectPortfolioSubmission
5740
6278
  }) => {
5741
6279
  await beforePlaceOrder?.();
5742
6280
  const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
@@ -5809,7 +6347,13 @@ var executeEntryOrder = async ({
5809
6347
  ...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
5810
6348
  orderId,
5811
6349
  signal,
5812
- ...beforeSubmit ? { beforeSubmit } : {}
6350
+ ...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
6351
+ ...beforeSubmit || markPortfolioSubmitted ? {
6352
+ beforeSubmit: async (context) => {
6353
+ await beforeSubmit?.(context);
6354
+ await markPortfolioSubmitted?.(context.qty);
6355
+ }
6356
+ } : {}
5813
6357
  });
5814
6358
  const orderAckTime = Date.now();
5815
6359
  const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
@@ -6797,7 +7341,7 @@ var handleProtectDecision = async ({
6797
7341
  }
6798
7342
  return decision.code;
6799
7343
  };
6800
- var executeEntryDecision = async ({
7344
+ var executeAdmittedEntryDecision = async ({
6801
7345
  connector,
6802
7346
  symbol,
6803
7347
  decision,
@@ -6811,7 +7355,9 @@ var executeEntryDecision = async ({
6811
7355
  ai,
6812
7356
  recordRuntimeJournal,
6813
7357
  invokeStageHooks,
6814
- notifyRuntimeError
7358
+ notifyRuntimeError,
7359
+ markPortfolioSubmitted,
7360
+ rejectPortfolioSubmission
6815
7361
  }) => {
6816
7362
  const signal = decision.signal;
6817
7363
  const beforePlaceOrder = async () => {
@@ -6858,6 +7404,8 @@ var executeEntryDecision = async ({
6858
7404
  ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
6859
7405
  signal,
6860
7406
  beforePlaceOrder,
7407
+ markPortfolioSubmitted,
7408
+ rejectPortfolioSubmission,
6861
7409
  recordRuntimeTrade: recordRuntimeJournal,
6862
7410
  executionLimits: resolveEntryExecutionLimits(
6863
7411
  hookCtx.strategyConfig,
@@ -6998,6 +7546,37 @@ var executeEntryDecision = async ({
6998
7546
  }
6999
7547
  return signal ?? decision.code;
7000
7548
  };
7549
+ async function executeEntryDecision(params) {
7550
+ if (!params.portfolio) return executeAdmittedEntryDecision(params);
7551
+ const { decision, portfolio } = params;
7552
+ const signal = decision.signal;
7553
+ if (!signal || decision.orderPlan.positionIntent === "increase")
7554
+ return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
7555
+ signal.orderId ??= createRuntimeOrderId(signal.strategy);
7556
+ const result = await portfolio.submit(
7557
+ params.portfolioTime ?? decision.entryContext.timestamp,
7558
+ {
7559
+ id: signal.orderId,
7560
+ signalId: signal.signalId,
7561
+ strategy: signal.strategy,
7562
+ symbol: params.symbol,
7563
+ direction: decision.entryContext.direction,
7564
+ qty: decision.orderPlan.qty,
7565
+ timestamp: decision.entryContext.timestamp
7566
+ },
7567
+ (markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
7568
+ ...params,
7569
+ markPortfolioSubmitted,
7570
+ rejectPortfolioSubmission
7571
+ })
7572
+ );
7573
+ if (result.reason) {
7574
+ signal.orderStatus = "failed";
7575
+ signal.orderFailureReason = result.reason;
7576
+ return signal;
7577
+ }
7578
+ return result.value;
7579
+ }
7001
7580
 
7002
7581
  // src/strategyRuntime.ts
7003
7582
  var cloneWithPropertyDescriptors = (value) => Object.create(
@@ -7025,6 +7604,7 @@ var createStrategyRuntime = ({
7025
7604
  };
7026
7605
  const creator = async ({
7027
7606
  userName,
7607
+ connectorName,
7028
7608
  config: baseConfig,
7029
7609
  symbol,
7030
7610
  universe: requestedUniverse,
@@ -7063,6 +7643,17 @@ var createStrategyRuntime = ({
7063
7643
  const projectConfig = await loadTradejsConfig(projectRoot);
7064
7644
  const projectHooks = projectConfig.hooks;
7065
7645
  const env = String(config.ENV ?? "BACKTEST");
7646
+ const portfolio = getPortfolioRuntime({
7647
+ connectorName,
7648
+ value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
7649
+ strategy: strategyName,
7650
+ connector,
7651
+ env,
7652
+ userName,
7653
+ accountId,
7654
+ deploymentId
7655
+ });
7656
+ const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
7066
7657
  const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
7067
7658
  const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
7068
7659
  if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
@@ -7361,6 +7952,20 @@ var createStrategyRuntime = ({
7361
7952
  interval: decisionInterval
7362
7953
  }) => {
7363
7954
  if (!baseContext) return void 0;
7955
+ const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
7956
+ if (profileSource) {
7957
+ const profile = await loadTickerProfileContext({
7958
+ ...profileSource,
7959
+ projectRoot,
7960
+ symbol: decisionSymbol,
7961
+ timestamp: candle.timestamp
7962
+ });
7963
+ if (!profile)
7964
+ throw new Error(
7965
+ "Required ticker profile is unavailable at decision time"
7966
+ );
7967
+ baseContext = withTickerProfile(baseContext, profile);
7968
+ }
7364
7969
  if (!coreContextRequirements.has("hyperliquidWhales")) {
7365
7970
  return baseContext;
7366
7971
  }
@@ -7528,6 +8133,8 @@ var createStrategyRuntime = ({
7528
8133
  runtime: pending2.runtime
7529
8134
  });
7530
8135
  return executeEntryDecision({
8136
+ portfolio,
8137
+ portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
7531
8138
  connector,
7532
8139
  symbol,
7533
8140
  decision: pending2.decision,
@@ -7545,6 +8152,10 @@ var createStrategyRuntime = ({
7545
8152
  });
7546
8153
  };
7547
8154
  const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
8155
+ if (portfolioContext)
8156
+ await portfolioContext(
8157
+ env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
8158
+ );
7548
8159
  appendCurrentMarketData(candle, btcCandle, options.ethCandle);
7549
8160
  const ethCandle = resolveEthCandle(candle, options.ethCandle);
7550
8161
  indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
@@ -7715,6 +8326,13 @@ var createStrategyRuntime = ({
7715
8326
  });
7716
8327
  const signal = decision.signal;
7717
8328
  if (signal) {
8329
+ if (portfolio)
8330
+ signal.additionalIndicators = {
8331
+ ...signal.additionalIndicators,
8332
+ portfolioContext: await portfolioContext(
8333
+ env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
8334
+ )
8335
+ };
7718
8336
  if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
7719
8337
  if (universe) signal.universe = universe;
7720
8338
  if (assetClass) signal.assetClass = assetClass;
@@ -7954,6 +8572,8 @@ var createStrategyRuntime = ({
7954
8572
  });
7955
8573
  }
7956
8574
  return executeEntryDecision({
8575
+ portfolio,
8576
+ portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
7957
8577
  connector,
7958
8578
  symbol,
7959
8579
  decision,
@@ -8065,7 +8685,7 @@ var normalizeForCanonicalJson = (value) => {
8065
8685
  }
8066
8686
  return value;
8067
8687
  };
8068
- var revision = (prefix, value) => `${prefix}:${(0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(normalizeForCanonicalJson(value))).digest("hex").slice(0, 16)}`;
8688
+ var revision = (prefix, value) => `${prefix}:${(0, import_node_crypto5.createHash)("sha256").update(JSON.stringify(normalizeForCanonicalJson(value))).digest("hex").slice(0, 16)}`;
8069
8689
  var computeStrategyRevision = ({
8070
8690
  strategyName,
8071
8691
  strategyPackage,
@@ -8669,13 +9289,13 @@ var assertCurrentRuntimeEvidenceArtifact = (artifact) => {
8669
9289
  throw new Error(`Runtime evidence payload ${field} must be an array`);
8670
9290
  }
8671
9291
  }
8672
- const snapshots = new Map(
9292
+ const snapshots2 = new Map(
8673
9293
  activeStrategies.map((strategy) => [strategy.strategyName, strategy])
8674
9294
  );
8675
9295
  const assertRows = (label, rows, lineageField) => {
8676
9296
  for (const row of rows) {
8677
9297
  const strategyName = String(row.strategy ?? "").trim();
8678
- const strategy = snapshots.get(strategyName);
9298
+ const strategy = snapshots2.get(strategyName);
8679
9299
  if (!strategy || row.deploymentId !== deployment.id || row.accountId !== deployment.accountId || !runtimeLineageMatchesStrategySnapshot({
8680
9300
  lineage: isRecord2(row[lineageField]) ? row[lineageField] : void 0,
8681
9301
  deployment,
@@ -8849,7 +9469,7 @@ var RUNTIME_EVIDENCE_MANIFEST_FILE = "manifest.json";
8849
9469
  var RUNTIME_EVIDENCE_PAYLOAD_FILE = "runtime-evidence.json";
8850
9470
  var RUNTIME_EVIDENCE_COMPLETE_FILE = ".complete";
8851
9471
  var asRecord2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
8852
- var sha256 = (value) => (0, import_node_crypto5.createHash)("sha256").update(value).digest("hex");
9472
+ var sha256 = (value) => (0, import_node_crypto6.createHash)("sha256").update(value).digest("hex");
8853
9473
  var runtimeEvidenceIdentitySha256 = (artifact) => {
8854
9474
  const identityArtifact = { ...artifact };
8855
9475
  delete identityArtifact.generatedAt;
@@ -8966,7 +9586,7 @@ var publishRuntimeEvidenceBundle = async ({
8966
9586
  const bundleDir = import_node_path3.default.join(readyRoot, artifactId);
8967
9587
  const temporaryDir = import_node_path3.default.join(
8968
9588
  incomingRoot,
8969
- `${artifactId}.tmp-${(0, import_node_crypto5.randomUUID)()}`
9589
+ `${artifactId}.tmp-${(0, import_node_crypto6.randomUUID)()}`
8970
9590
  );
8971
9591
  await import_promises3.default.mkdir(temporaryDir, { recursive: true });
8972
9592
  try {
@@ -9067,11 +9687,11 @@ var discoverRuntimeEvidenceBundles = async (rootDir) => {
9067
9687
  };
9068
9688
 
9069
9689
  // src/diagnostics/runtimeEvidenceProducer.ts
9070
- var import_node_crypto6 = require("crypto");
9690
+ var import_node_crypto7 = require("crypto");
9071
9691
  var import_promises4 = __toESM(require("fs/promises"));
9072
9692
  var import_node_path4 = __toESM(require("path"));
9073
9693
  var isRecord3 = (value) => value != null && typeof value === "object" && !Array.isArray(value);
9074
- var sha2562 = (value) => (0, import_node_crypto6.createHash)("sha256").update(value).digest("hex");
9694
+ var sha2562 = (value) => (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
9075
9695
  var parseRuntimeEvidenceProducer = (value) => {
9076
9696
  const producer = isRecord3(value) ? value : null;
9077
9697
  const runtimePackageManifest = isRecord3(producer?.runtimePackageManifest) ? producer.runtimePackageManifest : null;
@@ -9126,7 +9746,7 @@ var resolveRuntimeEvidenceProducer = async ({
9126
9746
  };
9127
9747
 
9128
9748
  // src/diagnostics/runtimeFeedbackArtifacts.ts
9129
- var import_node_crypto7 = require("crypto");
9749
+ var import_node_crypto8 = require("crypto");
9130
9750
  var import_promises5 = __toESM(require("fs/promises"));
9131
9751
  var import_node_path5 = __toESM(require("path"));
9132
9752
  var RUNTIME_FEEDBACK_MANIFEST_FILE = "manifest.json";
@@ -9134,7 +9754,7 @@ var RUNTIME_FEEDBACK_REPLAY_FILE = "replay-runtime-evidence.json";
9134
9754
  var RUNTIME_FEEDBACK_LOG_FILE = "replay.log";
9135
9755
  var RUNTIME_FEEDBACK_COMPLETE_FILE = ".complete";
9136
9756
  var isRecord4 = (value) => value != null && typeof value === "object" && !Array.isArray(value);
9137
- var sha2563 = (value) => (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
9757
+ var sha2563 = (value) => (0, import_node_crypto8.createHash)("sha256").update(value).digest("hex");
9138
9758
  var safeSegment2 = (value, fallback) => {
9139
9759
  const normalized = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-");
9140
9760
  return normalized && normalized !== "." && normalized !== ".." ? normalized : fallback;
@@ -9409,7 +10029,7 @@ var assertMcpFeedbackContract = (version, tools) => {
9409
10029
  };
9410
10030
 
9411
10031
  // src/diagnostics/mcpBundleDownload.ts
9412
- var import_node_crypto8 = require("crypto");
10032
+ var import_node_crypto9 = require("crypto");
9413
10033
  var import_promises6 = __toESM(require("fs/promises"));
9414
10034
  var import_node_path6 = __toESM(require("path"));
9415
10035
  var McpReadTransportError = class extends Error {
@@ -9419,7 +10039,7 @@ var McpReadTransportError = class extends Error {
9419
10039
  }
9420
10040
  };
9421
10041
  var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9422
- var digest = (bytes) => (0, import_node_crypto8.createHash)("sha256").update(bytes).digest("hex");
10042
+ var digest = (bytes) => (0, import_node_crypto9.createHash)("sha256").update(bytes).digest("hex");
9423
10043
  var exists = async (file) => import_promises6.default.lstat(file).catch((error) => {
9424
10044
  if (error.code === "ENOENT") return void 0;
9425
10045
  throw error;