@tradejs/node 2.0.21 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/registry.js CHANGED
@@ -44,22 +44,20 @@ __export(registry_exports, {
44
44
  });
45
45
  module.exports = __toCommonJS(registry_exports);
46
46
 
47
- // src/strategy/manifests.ts
48
- var import_indicators2 = require("@tradejs/core/indicators");
49
- var import_logger11 = require("@tradejs/infra/logger");
50
-
51
47
  // src/strategyRuntime.ts
52
48
  var import_constants5 = require("@tradejs/core/constants");
53
49
  var import_strategies6 = require("@tradejs/core/strategies");
54
- var import_logger10 = require("@tradejs/infra/logger");
50
+ var import_logger11 = require("@tradejs/infra/logger");
55
51
 
56
52
  // src/strategyHelpers/runtime.ts
57
- var import_logger8 = require("@tradejs/infra/logger");
53
+ var import_logger9 = require("@tradejs/infra/logger");
58
54
  var import_constants3 = require("@tradejs/core/constants");
59
55
  var import_ml2 = require("@tradejs/infra/ml");
60
56
 
61
57
  // src/ai.ts
62
- var import_aiLanguages = require("@tradejs/infra/aiLanguages");
58
+ var import_aiLanguages = require("@tradejs/core/aiLanguages");
59
+ var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
60
+ var import_aiModels = require("@tradejs/core/aiModels");
63
61
  var import_redis = require("@tradejs/infra/redis");
64
62
  var import_userSettings = require("@tradejs/infra/userSettings");
65
63
 
@@ -635,198 +633,742 @@ var buildAiMarketContext = (signal) => ({
635
633
  }
636
634
  });
637
635
 
638
- // src/strategy/policyProfiles.ts
639
- var profileMatches = (profile, universe, assetClass) => {
640
- const { appliesTo } = profile;
641
- if (!appliesTo) return true;
642
- if (appliesTo.universes?.length && (!universe || !appliesTo.universes.includes(universe))) {
643
- return false;
644
- }
645
- if (appliesTo.assetClasses?.length && (!assetClass || !appliesTo.assetClasses.includes(assetClass))) {
646
- return false;
647
- }
648
- return true;
649
- };
650
- var resolveStrategyPolicyProfile = (manifest, params) => {
651
- const profiles = manifest?.policyProfiles ?? [];
652
- if (!profiles.length) {
653
- const inferredId = params.profileId ?? (params.universe === "tradfi" ? "tradfi" : void 0);
654
- if (!inferredId) return void 0;
655
- if (inferredId !== "crypto" && inferredId !== "tradfi") {
656
- throw new Error(
657
- `Unknown policy profile "${inferredId}" for strategy "${manifest?.name}"`
658
- );
659
- }
660
- if (params.universe && inferredId !== params.universe) {
661
- throw new Error(
662
- `Policy profile "${inferredId}" is not compatible with ${params.universe}`
663
- );
664
- }
665
- return {
666
- id: inferredId,
667
- appliesTo: { universes: [inferredId] },
668
- marketDataRequirements: inferredId === "crypto" ? ["crypto.btcReference"] : [],
669
- ...manifest?.mlAdapter ? {
670
- entryRuntimeDefaults: {
671
- ml: {
672
- modelKey: inferredId === "crypto" ? manifest.name : `${manifest.name}:tradfi`
673
- }
674
- }
675
- } : {}
676
- };
636
+ // src/strategy/manifests.ts
637
+ var import_indicators = require("@tradejs/core/indicators");
638
+ var import_logger2 = require("@tradejs/infra/logger");
639
+
640
+ // src/tradejsConfig.ts
641
+ var import_fs = __toESM(require("fs"));
642
+ var import_path = __toESM(require("path"));
643
+ var import_url = require("url");
644
+ var import_config = require("@tradejs/core/config");
645
+ var import_logger = require("@tradejs/infra/logger");
646
+ var CONFIG_FILE_NAMES = [
647
+ "tradejs.config.ts",
648
+ "tradejs.config.mts",
649
+ "tradejs.config.js",
650
+ "tradejs.config.mjs",
651
+ "tradejs.config.cjs"
652
+ ];
653
+ var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
654
+ var cachedByCwd = /* @__PURE__ */ new Map();
655
+ var announcedConfigFile = /* @__PURE__ */ new Set();
656
+ var tsNodeRegistered = false;
657
+ var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
658
+ var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
659
+ var getTradejsProjectCwd = (cwd) => {
660
+ const explicit = String(cwd ?? "").trim();
661
+ if (explicit) {
662
+ return import_path.default.resolve(explicit);
677
663
  }
678
- if (params.profileId) {
679
- const profile = profiles.find(({ id }) => id === params.profileId);
680
- if (!profile) {
681
- throw new Error(
682
- `Unknown policy profile "${params.profileId}" for strategy "${manifest?.name}"`
683
- );
684
- }
685
- if (!profileMatches(profile, params.universe, params.assetClass)) {
686
- throw new Error(
687
- `Policy profile "${params.profileId}" is not compatible with ${params.universe ?? "unknown"}:${params.assetClass ?? "unknown"}`
688
- );
689
- }
690
- return profile;
664
+ const fromEnv = String(process.env.PROJECT_CWD || "").trim();
665
+ if (fromEnv) {
666
+ return import_path.default.resolve(fromEnv);
691
667
  }
692
- const matching = profiles.filter(
693
- (profile) => profileMatches(profile, params.universe, params.assetClass)
694
- );
695
- const defaultProfile = matching.find(
696
- ({ id }) => id === manifest?.defaultPolicyProfileId
697
- );
698
- return defaultProfile ?? matching[0];
668
+ return process.cwd();
699
669
  };
700
- var getStrategyProfileAiAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.aiAdapter ?? manifest?.aiAdapter;
701
- var getStrategyProfileMlAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.mlAdapter ?? manifest?.mlAdapter;
702
-
703
- // src/strategyAdapters/ai.ts
704
- var toRecord2 = (value) => {
705
- if (!value || typeof value !== "object" || Array.isArray(value)) {
670
+ var normalizeConfig = (rawConfig) => {
671
+ if (!rawConfig || typeof rawConfig !== "object") {
706
672
  return {};
707
673
  }
708
- return value;
709
- };
710
- var buildBaseAiPayload = (signal) => {
711
- const additionalIndicators = {
712
- ...toRecord2(signal.additionalIndicators),
713
- marketContext: buildAiMarketContext(signal)
714
- };
674
+ const config = rawConfig;
675
+ const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
676
+ const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
677
+ const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
678
+ const hooks = (0, import_config.normalizeTradejsConfigHooks)(
679
+ config.hooks
680
+ );
715
681
  return {
716
- signal: {
717
- symbol: signal.symbol,
718
- signalId: signal.signalId,
719
- interval: signal.interval,
720
- direction: signal.direction,
721
- timestamp: signal.timestamp,
722
- strategy: signal.strategy,
723
- prices: {
724
- currentPrice: signal.prices.currentPrice,
725
- takeProfitPrice: signal.prices.takeProfitPrice,
726
- stopLossPrice: signal.prices.stopLossPrice
727
- }
728
- },
729
- figures: trimSeriesDeep(signal.figures ?? {}),
730
- indicators: buildCompactAiIndicatorsSnapshot(signal.indicators),
731
- additionalIndicators: trimSeriesDeep(additionalIndicators)
682
+ strategies: strategies2,
683
+ indicators,
684
+ connectors,
685
+ ...hooks ? { hooks } : {}
732
686
  };
733
687
  };
734
- var defaultAiAdapter = {};
735
- var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
736
- var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
737
- var buildAiPayloadByStrategy = (signal) => {
738
- const basePayload = buildBaseAiPayload(signal);
739
- const adapter = getSignalAiAdapter(signal);
740
- return adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
741
- };
742
- var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
743
- var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
744
- signal,
745
- payload
746
- }) ?? "";
747
- var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => getSignalAiAdapter(signal).postProcessAnalysis?.({
748
- signal,
749
- payload,
750
- analysis
751
- }) ?? analysis;
752
- var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => {
753
- const adapter = getSignalAiAdapter(signal);
754
- const strategyAnalysis = adapter.postProcessAnalysis?.({ signal, payload, analysis }) ?? analysis;
755
- return adapter.postProcessLocalAnalysis?.({
756
- signal,
757
- payload,
758
- analysis: strategyAnalysis
759
- }) ?? strategyAnalysis;
760
- };
761
-
762
- // src/ai.ts
763
- var parseAIResponse = (input) => {
764
- try {
765
- if (typeof input === "object" && input !== null) return input;
766
- const match = input.match(/\{[\s\S]*\}/);
767
- if (!match) throw new Error("JSON block not found");
768
- return JSON.parse(match[0]);
769
- } catch (err) {
770
- console.error("Failed to parse AI response:", err);
771
- console.log("Raw AI response:", input);
772
- return {};
688
+ var getNodeCreateRequire = () => {
689
+ const builtinModule = process.getBuiltinModule?.("module");
690
+ if (typeof builtinModule?.createRequire === "function") {
691
+ return builtinModule.createRequire;
773
692
  }
693
+ throw new TypeError("module.createRequire is not available");
774
694
  };
775
- var normalizeResponseContent = (content) => {
776
- if (typeof content === "string" || content && typeof content === "object") {
777
- if (typeof content !== "object" || !Array.isArray(content)) {
778
- return content;
779
- }
780
- }
781
- if (Array.isArray(content)) {
782
- const text = content.map((part) => typeof part?.text === "string" ? part.text : "").join("\n").trim();
783
- return text;
695
+ var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
696
+ var ensureTsNodeRegistered = async () => {
697
+ if (tsNodeRegistered) {
698
+ return;
784
699
  }
785
- return String(content ?? "");
786
- };
787
- var normalizeAnalysis = (raw) => {
788
- const direction = raw?.direction === "LONG" || raw?.direction === "SHORT" ? raw.direction : null;
789
- const qualityNum = typeof raw?.quality === "number" ? Math.max(1, Math.min(5, Math.round(raw.quality))) : void 0;
790
- const toNumberOrNull = (value) => {
791
- if (typeof value === "number" && Number.isFinite(value)) return value;
792
- if (typeof value === "string" && value.trim()) {
793
- const parsed = Number(value);
794
- if (Number.isFinite(parsed)) return parsed;
700
+ const tsNodeModule = await import("ts-node");
701
+ const tsNode = tsNodeModule.default ?? tsNodeModule;
702
+ tsNode.register?.({
703
+ transpileOnly: true,
704
+ compilerOptions: {
705
+ module: "Node16",
706
+ moduleResolution: "node16"
795
707
  }
796
- return null;
797
- };
798
- const toText = (value) => typeof value === "string" ? value.slice(0, 400) : void 0;
799
- return {
800
- direction,
801
- quality: qualityNum,
802
- needRetest: Boolean(raw?.needRetest),
803
- retestPrice: toNumberOrNull(raw?.retestPrice),
804
- takeProfitPrice: toNumberOrNull(raw?.takeProfitPrice),
805
- stopLossPrice: toNumberOrNull(raw?.stopLossPrice),
806
- setup: toText(raw?.setup),
807
- confirmations: toText(raw?.confirmations),
808
- btcContext: toText(raw?.btcContext),
809
- retestPlan: toText(raw?.retestPlan),
810
- riskLevels: toText(raw?.riskLevels),
811
- qualityReason: toText(raw?.qualityReason),
812
- triggerInvalidation: toText(raw?.triggerInvalidation),
813
- comment: typeof raw?.comment === "string" ? raw.comment.slice(0, 1024) : ""
814
- };
708
+ });
709
+ tsNodeRegistered = true;
815
710
  };
816
- var asRecord = (value) => {
817
- if (!value || typeof value !== "object" || Array.isArray(value)) {
818
- return null;
711
+ var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
712
+ const projectRoot = getTradejsProjectCwd(cwd);
713
+ if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
714
+ return;
819
715
  }
820
- return value;
821
- };
822
- var getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
823
- var getDeterministicQuality = (gateContext) => {
824
- const deterministicQuality = Number(gateContext?.deterministicQuality);
825
- if (Number.isFinite(deterministicQuality)) {
826
- return Math.max(1, Math.min(5, Math.round(deterministicQuality)));
716
+ const tsconfigPathsModule = await import("tsconfig-paths");
717
+ const loadConfig = tsconfigPathsModule.loadConfig;
718
+ const register = tsconfigPathsModule.register;
719
+ if (typeof loadConfig !== "function" || typeof register !== "function") {
720
+ return;
827
721
  }
828
- const maxAllowedQuality = Number(gateContext?.maxAllowedQuality);
829
- if (Number.isFinite(maxAllowedQuality)) {
722
+ const loadedConfig = loadConfig(projectRoot);
723
+ if (loadedConfig.resultType !== "success") {
724
+ return;
725
+ }
726
+ register({
727
+ baseUrl: loadedConfig.absoluteBaseUrl,
728
+ paths: loadedConfig.paths,
729
+ addMatchAll: false
730
+ });
731
+ tsconfigPathsRegisteredByCwd.add(projectRoot);
732
+ };
733
+ var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
734
+ const projectRoot = getTradejsProjectCwd(cwd);
735
+ const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
736
+ if (cachedMatcher) {
737
+ const resolved2 = cachedMatcher(moduleName);
738
+ return resolved2 || null;
739
+ }
740
+ const tsconfigPathsModule = await import("tsconfig-paths");
741
+ const loadConfig = tsconfigPathsModule.loadConfig;
742
+ const createMatchPath = tsconfigPathsModule.createMatchPath;
743
+ if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
744
+ return null;
745
+ }
746
+ const loadedConfig = loadConfig(projectRoot);
747
+ if (loadedConfig.resultType !== "success") {
748
+ return null;
749
+ }
750
+ const matchPath = createMatchPath(
751
+ loadedConfig.absoluteBaseUrl,
752
+ loadedConfig.paths
753
+ );
754
+ const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
755
+ ".ts",
756
+ ".tsx",
757
+ ".mts",
758
+ ".cts",
759
+ ".js",
760
+ ".jsx",
761
+ ".mjs",
762
+ ".cjs",
763
+ ".json"
764
+ ]) || "";
765
+ tsconfigPathMatchersByCwd.set(projectRoot, matcher);
766
+ const resolved = matcher(moduleName);
767
+ return resolved || null;
768
+ };
769
+ var toImportSpecifier = (moduleName) => {
770
+ if (moduleName.startsWith("file://")) {
771
+ return moduleName;
772
+ }
773
+ if (import_path.default.isAbsolute(moduleName)) {
774
+ return (0, import_url.pathToFileURL)(moduleName).href;
775
+ }
776
+ return moduleName;
777
+ };
778
+ var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
779
+ var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
780
+ var isBareModuleSpecifier = (moduleName) => {
781
+ const normalized = String(moduleName ?? "").trim();
782
+ if (!normalized) {
783
+ return false;
784
+ }
785
+ if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
786
+ return false;
787
+ }
788
+ return true;
789
+ };
790
+ var importConfigFile = async (configFilePath) => {
791
+ const ext = import_path.default.extname(configFilePath).toLowerCase();
792
+ const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
793
+ if (ext === ".ts" || ext === ".mts") {
794
+ const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
795
+ await ensureTsNodeRegistered();
796
+ await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
797
+ return requireFn(configFilePath);
798
+ }
799
+ return import(
800
+ /* webpackIgnore: true */
801
+ configFileUrl
802
+ );
803
+ };
804
+ var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
805
+ const normalized = String(moduleName ?? "").trim();
806
+ if (!normalized) {
807
+ return {};
808
+ }
809
+ let modulePath = normalized;
810
+ if (normalized.startsWith("file://")) {
811
+ try {
812
+ modulePath = (0, import_url.fileURLToPath)(normalized);
813
+ } catch {
814
+ modulePath = normalized;
815
+ }
816
+ }
817
+ const requireFn = getRequireFn(
818
+ import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
819
+ );
820
+ if (isTsModulePath(modulePath)) {
821
+ await ensureTsNodeRegistered();
822
+ await ensureTsconfigPathsRegistered(cwd);
823
+ return requireFn(modulePath);
824
+ }
825
+ if (isBareModuleSpecifier(normalized)) {
826
+ await ensureTsconfigPathsRegistered(cwd);
827
+ try {
828
+ return requireFn(normalized);
829
+ } catch (error) {
830
+ const resolvedByTsconfig = await resolveTsconfigPathModule(
831
+ normalized,
832
+ cwd
833
+ );
834
+ if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
835
+ return requireFn(resolvedByTsconfig);
836
+ }
837
+ throw error;
838
+ }
839
+ }
840
+ try {
841
+ return await import(
842
+ /* webpackIgnore: true */
843
+ toImportSpecifier(normalized)
844
+ );
845
+ } catch (error) {
846
+ if (isTsModulePath(modulePath)) {
847
+ await ensureTsNodeRegistered();
848
+ await ensureTsconfigPathsRegistered(cwd);
849
+ return requireFn(modulePath);
850
+ }
851
+ throw error;
852
+ }
853
+ };
854
+ var resolveExportedConfig = (moduleExports) => {
855
+ const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
856
+ return normalizeConfig(candidate);
857
+ };
858
+ var findConfigFilePath = (cwd) => {
859
+ let currentDir = import_path.default.resolve(cwd);
860
+ while (true) {
861
+ for (const fileName of CONFIG_FILE_NAMES) {
862
+ const fullPath = import_path.default.join(currentDir, fileName);
863
+ if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
864
+ return fullPath;
865
+ }
866
+ }
867
+ const parentDir = import_path.default.dirname(currentDir);
868
+ if (parentDir === currentDir) {
869
+ return null;
870
+ }
871
+ currentDir = parentDir;
872
+ }
873
+ };
874
+ var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
875
+ const normalized = String(moduleName ?? "").trim();
876
+ if (!normalized) {
877
+ return "";
878
+ }
879
+ if (normalized.startsWith("file://")) {
880
+ try {
881
+ return (0, import_url.fileURLToPath)(normalized);
882
+ } catch {
883
+ return normalized;
884
+ }
885
+ }
886
+ if (import_path.default.isAbsolute(normalized)) {
887
+ return normalized;
888
+ }
889
+ if (isRelativeModulePath(normalized)) {
890
+ return import_path.default.resolve(cwd, normalized);
891
+ }
892
+ return normalized;
893
+ };
894
+ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
895
+ const cached = cachedByCwd.get(cwd);
896
+ if (cached) {
897
+ return cached;
898
+ }
899
+ const configFilePath = findConfigFilePath(cwd);
900
+ if (!configFilePath) {
901
+ cachedByCwd.set(cwd, {});
902
+ return {};
903
+ }
904
+ try {
905
+ const moduleExports = await importConfigFile(configFilePath);
906
+ const config = resolveExportedConfig(moduleExports);
907
+ cachedByCwd.set(cwd, config);
908
+ if (!announcedConfigFile.has(configFilePath)) {
909
+ announcedConfigFile.add(configFilePath);
910
+ import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
911
+ }
912
+ return config;
913
+ } catch (error) {
914
+ import_logger.logger.log(
915
+ "warn",
916
+ "Failed to load TradeJS config from %s: %s",
917
+ configFilePath,
918
+ String(error)
919
+ );
920
+ cachedByCwd.set(cwd, {});
921
+ return {};
922
+ }
923
+ };
924
+
925
+ // src/strategy/manifests.ts
926
+ var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
927
+ var sharedRegistryScope = globalThis;
928
+ var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
929
+ registryStateByProjectRoot: /* @__PURE__ */ new Map()
930
+ });
931
+ var createStrategyRegistryState = () => ({
932
+ strategyCreators: /* @__PURE__ */ new Map(),
933
+ strategyManifestsMap: /* @__PURE__ */ new Map(),
934
+ strategyEntriesMap: /* @__PURE__ */ new Map(),
935
+ pluginsLoadPromise: null
936
+ });
937
+ var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
938
+ var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
939
+ const projectRoot = getTradejsProjectCwd(cwd);
940
+ let state = registryStateByProjectRoot.get(projectRoot);
941
+ if (!state) {
942
+ state = createStrategyRegistryState();
943
+ registryStateByProjectRoot.set(projectRoot, state);
944
+ }
945
+ return {
946
+ projectRoot,
947
+ state
948
+ };
949
+ };
950
+ var toUniqueModules = (modules = []) => [
951
+ ...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
952
+ ];
953
+ var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
954
+ const config = await loadTradejsConfig(cwd);
955
+ return {
956
+ strategyModules: toUniqueModules(config.strategies),
957
+ indicatorModules: toUniqueModules(config.indicators)
958
+ };
959
+ };
960
+ var extractModuleEntries = (moduleExport, key) => {
961
+ if (!moduleExport || typeof moduleExport !== "object") {
962
+ return null;
963
+ }
964
+ const candidate = moduleExport;
965
+ if (Array.isArray(candidate[key])) {
966
+ return candidate[key];
967
+ }
968
+ const defaultExport = candidate.default;
969
+ if (defaultExport && Array.isArray(defaultExport[key])) {
970
+ return defaultExport[key];
971
+ }
972
+ return null;
973
+ };
974
+ var extractStrategyPluginDefinition = (moduleExport) => {
975
+ const strategyEntries = extractModuleEntries(
976
+ moduleExport,
977
+ "strategyEntries"
978
+ );
979
+ return strategyEntries ? { strategyEntries } : null;
980
+ };
981
+ var extractIndicatorPluginDefinition = (moduleExport) => {
982
+ const indicatorEntries = extractModuleEntries(
983
+ moduleExport,
984
+ "indicatorEntries"
985
+ );
986
+ return indicatorEntries ? { indicatorEntries } : null;
987
+ };
988
+ var registerEntries = (entries, source, state) => {
989
+ for (const entry of entries) {
990
+ const strategyName = entry.manifest?.name;
991
+ if (!strategyName) {
992
+ import_logger2.logger.warn("Skip strategy entry without name from %s", source);
993
+ continue;
994
+ }
995
+ if (state.strategyCreators.has(strategyName)) {
996
+ import_logger2.logger.warn(
997
+ 'Skip duplicate strategy "%s" from %s: already registered',
998
+ strategyName,
999
+ source
1000
+ );
1001
+ continue;
1002
+ }
1003
+ state.strategyManifestsMap.set(strategyName, entry.manifest);
1004
+ state.strategyEntriesMap.set(strategyName, entry);
1005
+ materializeStrategyCreator(strategyName, state);
1006
+ }
1007
+ };
1008
+ var materializeStrategyCreator = (strategyName, state) => {
1009
+ if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
1010
+ return;
1011
+ }
1012
+ const entry = state.strategyEntriesMap.get(strategyName);
1013
+ if (!entry) return;
1014
+ state.strategyCreators.set(
1015
+ strategyName,
1016
+ sharedStrategyRegistry.strategyRuntimeFactory({
1017
+ strategyName,
1018
+ defaults: entry.defaults,
1019
+ createCore: entry.createCore,
1020
+ manifest: entry.manifest,
1021
+ detectorKey: entry.detectorKey,
1022
+ detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
1023
+ resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
1024
+ })
1025
+ );
1026
+ };
1027
+ var setStrategyRuntimeFactory = (factory) => {
1028
+ sharedStrategyRegistry.strategyRuntimeFactory = factory;
1029
+ for (const state of registryStateByProjectRoot.values()) {
1030
+ for (const strategyName of state.strategyEntriesMap.keys()) {
1031
+ materializeStrategyCreator(strategyName, state);
1032
+ }
1033
+ }
1034
+ };
1035
+ var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
1036
+ if (typeof importTradejsModule === "function") {
1037
+ return importTradejsModule(moduleName, cwd);
1038
+ }
1039
+ return import(
1040
+ /* webpackIgnore: true */
1041
+ moduleName
1042
+ );
1043
+ };
1044
+ var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
1045
+ const { projectRoot, state } = getStrategyRegistryState(cwd);
1046
+ if (!state.pluginsLoadPromise) {
1047
+ (0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
1048
+ state.pluginsLoadPromise = (async () => {
1049
+ const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
1050
+ const strategySet = new Set(strategyModules);
1051
+ const indicatorSet = new Set(indicatorModules);
1052
+ const pluginModuleNames = [
1053
+ .../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
1054
+ ];
1055
+ if (!pluginModuleNames.length) {
1056
+ return;
1057
+ }
1058
+ for (const moduleName of pluginModuleNames) {
1059
+ try {
1060
+ const resolvedModuleName = resolvePluginModuleSpecifier(
1061
+ moduleName,
1062
+ projectRoot
1063
+ );
1064
+ const moduleExport = await importStrategyPluginModule(
1065
+ resolvedModuleName,
1066
+ projectRoot
1067
+ );
1068
+ if (strategySet.has(moduleName)) {
1069
+ const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
1070
+ if (!pluginDefinition) {
1071
+ import_logger2.logger.warn(
1072
+ 'Skip strategy plugin "%s": export { strategyEntries } is missing',
1073
+ moduleName
1074
+ );
1075
+ } else {
1076
+ registerEntries(
1077
+ pluginDefinition.strategyEntries,
1078
+ moduleName,
1079
+ state
1080
+ );
1081
+ }
1082
+ }
1083
+ if (indicatorSet.has(moduleName)) {
1084
+ const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
1085
+ if (!indicatorPluginDefinition) {
1086
+ import_logger2.logger.warn(
1087
+ 'Skip indicator plugin "%s": export { indicatorEntries } is missing',
1088
+ moduleName
1089
+ );
1090
+ } else {
1091
+ (0, import_indicators.registerIndicatorEntries)(
1092
+ indicatorPluginDefinition.indicatorEntries,
1093
+ moduleName,
1094
+ projectRoot
1095
+ );
1096
+ }
1097
+ }
1098
+ if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
1099
+ import_logger2.logger.warn(
1100
+ 'Skip plugin "%s": no strategy/indicator sections requested in config',
1101
+ moduleName
1102
+ );
1103
+ }
1104
+ } catch (error) {
1105
+ import_logger2.logger.warn(
1106
+ 'Failed to load plugin "%s": %s',
1107
+ moduleName,
1108
+ String(error)
1109
+ );
1110
+ }
1111
+ }
1112
+ })();
1113
+ }
1114
+ await state.pluginsLoadPromise;
1115
+ };
1116
+ var ensureIndicatorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => ensureStrategyPluginsLoaded(cwd);
1117
+ var getStrategyCreator = async (name, cwd = getTradejsProjectCwd()) => {
1118
+ await ensureStrategyPluginsLoaded(cwd);
1119
+ const { state } = getStrategyRegistryState(cwd);
1120
+ return state.strategyCreators.get(name);
1121
+ };
1122
+ var getAvailableStrategyNames = async (cwd = getTradejsProjectCwd()) => {
1123
+ await ensureStrategyPluginsLoaded(cwd);
1124
+ const { state } = getStrategyRegistryState(cwd);
1125
+ return [...state.strategyCreators.keys()].sort((a, b) => a.localeCompare(b));
1126
+ };
1127
+ var getRegisteredStrategies = (cwd = getTradejsProjectCwd()) => {
1128
+ const { state } = getStrategyRegistryState(cwd);
1129
+ return Object.fromEntries(state.strategyCreators.entries());
1130
+ };
1131
+ var getRegisteredManifests = (cwd = getTradejsProjectCwd()) => {
1132
+ const { state } = getStrategyRegistryState(cwd);
1133
+ return [...state.strategyManifestsMap.values()];
1134
+ };
1135
+ var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
1136
+ if (!name) {
1137
+ return void 0;
1138
+ }
1139
+ const { state } = getStrategyRegistryState(cwd);
1140
+ return state.strategyManifestsMap.get(name);
1141
+ };
1142
+ var isKnownStrategy = (name, cwd = getTradejsProjectCwd()) => {
1143
+ const { state } = getStrategyRegistryState(cwd);
1144
+ return state.strategyCreators.has(name);
1145
+ };
1146
+ var registerStrategyEntries = (entries, cwd = getTradejsProjectCwd()) => {
1147
+ const { state } = getStrategyRegistryState(cwd);
1148
+ registerEntries(entries, "runtime", state);
1149
+ };
1150
+ var resetStrategyRegistryCache = (cwd) => {
1151
+ const normalizedCwd = String(cwd ?? "").trim();
1152
+ if (!normalizedCwd) {
1153
+ registryStateByProjectRoot.clear();
1154
+ (0, import_indicators.resetIndicatorRegistryCache)();
1155
+ return;
1156
+ }
1157
+ const projectRoot = getTradejsProjectCwd(normalizedCwd);
1158
+ registryStateByProjectRoot.delete(projectRoot);
1159
+ (0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
1160
+ };
1161
+ var strategies = new Proxy(
1162
+ {},
1163
+ {
1164
+ get: (_target, property) => {
1165
+ if (typeof property !== "string") {
1166
+ return void 0;
1167
+ }
1168
+ return getStrategyRegistryState().state.strategyCreators.get(property);
1169
+ },
1170
+ ownKeys: () => {
1171
+ return [...getStrategyRegistryState().state.strategyCreators.keys()];
1172
+ },
1173
+ getOwnPropertyDescriptor: () => ({
1174
+ enumerable: true,
1175
+ configurable: true
1176
+ })
1177
+ }
1178
+ );
1179
+
1180
+ // src/strategy/policyProfiles.ts
1181
+ var profileMatches = (profile, universe, assetClass) => {
1182
+ const { appliesTo } = profile;
1183
+ if (!appliesTo) return true;
1184
+ if (appliesTo.universes?.length && (!universe || !appliesTo.universes.includes(universe))) {
1185
+ return false;
1186
+ }
1187
+ if (appliesTo.assetClasses?.length && (!assetClass || !appliesTo.assetClasses.includes(assetClass))) {
1188
+ return false;
1189
+ }
1190
+ return true;
1191
+ };
1192
+ var resolveStrategyPolicyProfile = (manifest, params) => {
1193
+ const profiles = manifest?.policyProfiles ?? [];
1194
+ if (!profiles.length) {
1195
+ const inferredId = params.profileId ?? (params.universe === "tradfi" ? "tradfi" : void 0);
1196
+ if (!inferredId) return void 0;
1197
+ if (inferredId !== "crypto" && inferredId !== "tradfi") {
1198
+ throw new Error(
1199
+ `Unknown policy profile "${inferredId}" for strategy "${manifest?.name}"`
1200
+ );
1201
+ }
1202
+ if (params.universe && inferredId !== params.universe) {
1203
+ throw new Error(
1204
+ `Policy profile "${inferredId}" is not compatible with ${params.universe}`
1205
+ );
1206
+ }
1207
+ return {
1208
+ id: inferredId,
1209
+ appliesTo: { universes: [inferredId] },
1210
+ marketDataRequirements: inferredId === "crypto" ? ["crypto.btcReference"] : [],
1211
+ ...manifest?.mlAdapter ? {
1212
+ entryRuntimeDefaults: {
1213
+ ml: {
1214
+ modelKey: inferredId === "crypto" ? manifest.name : `${manifest.name}:tradfi`
1215
+ }
1216
+ }
1217
+ } : {}
1218
+ };
1219
+ }
1220
+ if (params.profileId) {
1221
+ const profile = profiles.find(({ id }) => id === params.profileId);
1222
+ if (!profile) {
1223
+ throw new Error(
1224
+ `Unknown policy profile "${params.profileId}" for strategy "${manifest?.name}"`
1225
+ );
1226
+ }
1227
+ if (!profileMatches(profile, params.universe, params.assetClass)) {
1228
+ throw new Error(
1229
+ `Policy profile "${params.profileId}" is not compatible with ${params.universe ?? "unknown"}:${params.assetClass ?? "unknown"}`
1230
+ );
1231
+ }
1232
+ return profile;
1233
+ }
1234
+ const matching = profiles.filter(
1235
+ (profile) => profileMatches(profile, params.universe, params.assetClass)
1236
+ );
1237
+ const defaultProfile = matching.find(
1238
+ ({ id }) => id === manifest?.defaultPolicyProfileId
1239
+ );
1240
+ return defaultProfile ?? matching[0];
1241
+ };
1242
+ var getStrategyProfileAiAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.aiAdapter ?? manifest?.aiAdapter;
1243
+ var getStrategyProfileMlAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.mlAdapter ?? manifest?.mlAdapter;
1244
+
1245
+ // src/strategyAdapters/ai.ts
1246
+ var toRecord2 = (value) => {
1247
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1248
+ return {};
1249
+ }
1250
+ return value;
1251
+ };
1252
+ var buildBaseAiPayload = (signal) => {
1253
+ const additionalIndicators = {
1254
+ ...toRecord2(signal.additionalIndicators),
1255
+ marketContext: buildAiMarketContext(signal)
1256
+ };
1257
+ return {
1258
+ signal: {
1259
+ symbol: signal.symbol,
1260
+ signalId: signal.signalId,
1261
+ interval: signal.interval,
1262
+ direction: signal.direction,
1263
+ timestamp: signal.timestamp,
1264
+ strategy: signal.strategy,
1265
+ prices: {
1266
+ currentPrice: signal.prices.currentPrice,
1267
+ takeProfitPrice: signal.prices.takeProfitPrice,
1268
+ stopLossPrice: signal.prices.stopLossPrice
1269
+ }
1270
+ },
1271
+ figures: trimSeriesDeep(signal.figures ?? {}),
1272
+ indicators: buildCompactAiIndicatorsSnapshot(signal.indicators),
1273
+ additionalIndicators: trimSeriesDeep(additionalIndicators)
1274
+ };
1275
+ };
1276
+ var defaultAiAdapter = {};
1277
+ var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
1278
+ var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
1279
+ var buildAiPayloadByStrategy = (signal) => {
1280
+ const basePayload = buildBaseAiPayload(signal);
1281
+ const adapter = getSignalAiAdapter(signal);
1282
+ return adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
1283
+ };
1284
+ var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
1285
+ var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
1286
+ signal,
1287
+ payload
1288
+ }) ?? "";
1289
+ var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => getSignalAiAdapter(signal).postProcessAnalysis?.({
1290
+ signal,
1291
+ payload,
1292
+ analysis
1293
+ }) ?? analysis;
1294
+ var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => {
1295
+ const adapter = getSignalAiAdapter(signal);
1296
+ const strategyAnalysis = adapter.postProcessAnalysis?.({ signal, payload, analysis }) ?? analysis;
1297
+ return adapter.postProcessLocalAnalysis?.({
1298
+ signal,
1299
+ payload,
1300
+ analysis: strategyAnalysis
1301
+ }) ?? strategyAnalysis;
1302
+ };
1303
+
1304
+ // src/ai.ts
1305
+ var parseAIResponse = (input) => {
1306
+ try {
1307
+ if (typeof input === "object" && input !== null) return input;
1308
+ const match = input.match(/\{[\s\S]*\}/);
1309
+ if (!match) throw new Error("JSON block not found");
1310
+ return JSON.parse(match[0]);
1311
+ } catch (err) {
1312
+ console.error("Failed to parse AI response:", err);
1313
+ console.log("Raw AI response:", input);
1314
+ return {};
1315
+ }
1316
+ };
1317
+ var normalizeResponseContent = (content) => {
1318
+ if (typeof content === "string" || content && typeof content === "object") {
1319
+ if (typeof content !== "object" || !Array.isArray(content)) {
1320
+ return content;
1321
+ }
1322
+ }
1323
+ if (Array.isArray(content)) {
1324
+ const text = content.map((part) => typeof part?.text === "string" ? part.text : "").join("\n").trim();
1325
+ return text;
1326
+ }
1327
+ return String(content ?? "");
1328
+ };
1329
+ var normalizeAnalysis = (raw) => {
1330
+ const direction = raw?.direction === "LONG" || raw?.direction === "SHORT" ? raw.direction : null;
1331
+ const qualityNum = typeof raw?.quality === "number" ? Math.max(1, Math.min(5, Math.round(raw.quality))) : void 0;
1332
+ const toNumberOrNull = (value) => {
1333
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1334
+ if (typeof value === "string" && value.trim()) {
1335
+ const parsed = Number(value);
1336
+ if (Number.isFinite(parsed)) return parsed;
1337
+ }
1338
+ return null;
1339
+ };
1340
+ const toText = (value) => typeof value === "string" ? value.slice(0, 400) : void 0;
1341
+ return {
1342
+ direction,
1343
+ quality: qualityNum,
1344
+ needRetest: Boolean(raw?.needRetest),
1345
+ retestPrice: toNumberOrNull(raw?.retestPrice),
1346
+ takeProfitPrice: toNumberOrNull(raw?.takeProfitPrice),
1347
+ stopLossPrice: toNumberOrNull(raw?.stopLossPrice),
1348
+ setup: toText(raw?.setup),
1349
+ confirmations: toText(raw?.confirmations),
1350
+ btcContext: toText(raw?.btcContext),
1351
+ retestPlan: toText(raw?.retestPlan),
1352
+ riskLevels: toText(raw?.riskLevels),
1353
+ qualityReason: toText(raw?.qualityReason),
1354
+ triggerInvalidation: toText(raw?.triggerInvalidation),
1355
+ comment: typeof raw?.comment === "string" ? raw.comment.slice(0, 1024) : ""
1356
+ };
1357
+ };
1358
+ var asRecord = (value) => {
1359
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1360
+ return null;
1361
+ }
1362
+ return value;
1363
+ };
1364
+ var getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
1365
+ var getDeterministicQuality = (gateContext) => {
1366
+ const deterministicQuality = Number(gateContext?.deterministicQuality);
1367
+ if (Number.isFinite(deterministicQuality)) {
1368
+ return Math.max(1, Math.min(5, Math.round(deterministicQuality)));
1369
+ }
1370
+ const maxAllowedQuality = Number(gateContext?.maxAllowedQuality);
1371
+ if (Number.isFinite(maxAllowedQuality)) {
830
1372
  return Math.max(1, Math.min(5, Math.round(maxAllowedQuality)));
831
1373
  }
832
1374
  return Array.isArray(gateContext?.approvalBlockReasons) && gateContext.approvalBlockReasons.length > 0 || Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length > 0 ? 2 : 3;
@@ -986,560 +1528,277 @@ Requirements for useful structured analysis:
986
1528
  - In \`retestPlan\`, avoid technical placeholders like \`needRetest=false @ null\`; write a human explanation.
987
1529
  - Do not simply restate JSON fields; add interpretation and decision logic.
988
1530
 
989
- Rules for using trimmed series (last 5 values):
990
- - Do not make strong long-term conclusions from only 5 points.
991
- - Use 4h and 1d series as brief context, not full history.
992
- - If the data is too limited for confidence, reduce quality and use cautious wording.
993
-
994
- Short few-shot examples:
995
- {"direction":"LONG","quality":4,"needRetest":true,"retestPrice":100.2,"takeProfitPrice":101.5,"stopLossPrice":98.9,"setup":"Likely trendline breakout upward, but the signal still needs a level check for confirmation.","confirmations":"The coin shows momentum support without obvious overheating, but confirmation is not fully clean yet.","btcContext":"BTC is neutral-to-supportive and does not conflict with the current LONG signal.","retestPlan":"The key level is 100.2; holding above it would confirm the signal structure.","riskLevels":"The supplied TP and SL remain on the correct sides of the current price and still look internally coherent.","qualityReason":"Quality=4 because the structure is solid, but an extra level confirmation is still preferable.","triggerInvalidation":"The structure confirms on a hold above the level and weakens on a move back under the line."}
996
- {"direction":null,"quality":2,"needRetest":false,"retestPrice":null,"takeProfitPrice":null,"stopLossPrice":null,"setup":"Touch or noise around the trendline without a convincing breakout.","confirmations":"Indicators are mixed and do not provide strong structural support.","btcContext":"BTC is either conflicting or not supportive of the current thesis.","retestPlan":"It is too early to define an extra level because a quality breakout is not present yet.","riskLevels":"The supplied levels should not be treated as confirmed while the structure remains weak.","qualityReason":"Quality=2 because timing is weak and confirmations are limited.","triggerInvalidation":"Wait for a clear breakout and confirmation from both the coin and BTC."}
997
-
998
- Return only the JSON object, with no extra characters.
999
- ${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
1000
- `;
1001
- var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
1002
- var getDeterministicAiGateContext = (payload) => {
1003
- const additionalIndicators = asRecord(payload.additionalIndicators);
1004
- const candidates = [
1005
- additionalIndicators,
1006
- ...Object.values(additionalIndicators ?? {}).map(asRecord)
1007
- ].filter((value) => Boolean(value));
1008
- return candidates.find(
1009
- (candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
1010
- ) ?? null;
1011
- };
1012
- var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
1013
- Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
1014
- This is a structure-classification and audit task, not execution advice. Determine whether the current structure confirms the existing signal, how structurally coherent it is right now, whether an extra confirmation level is needed, and whether the already supplied levels in \`payload.signal.prices\` still look internally coherent. Do not replace the original thesis with a new one and do not invent new levels; return only the requested JSON.
1015
-
1016
- Trade payload:
1017
- ${JSON.stringify(payload)}
1018
- ${buildAiHumanPromptAddonByStrategy(signal, payload)}
1019
- `;
1020
- var getAiInvocationError = (error) => {
1021
- const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
1022
- const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
1023
- details
1024
- );
1025
- const wrapped = new Error(
1026
- isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
1027
- );
1028
- wrapped.cause = error;
1029
- return wrapped;
1030
- };
1031
- var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
1032
- var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
1033
- var userSettingsCache = /* @__PURE__ */ new Map();
1034
- var aiModelCache = /* @__PURE__ */ new Map();
1035
- var getAiModelCacheKey = (userName, modelName) => `${userName}::${modelName}`;
1036
- var resolveAiModelName = (settings, requestedModelName) => {
1037
- const explicitModelName = typeof requestedModelName === "string" ? requestedModelName.trim() : "";
1038
- if (explicitModelName) {
1039
- return explicitModelName;
1040
- }
1041
- const settingsModelName = typeof settings.AI_MODEL === "string" ? settings.AI_MODEL.trim() : "";
1042
- return settingsModelName || DEFAULT_AI_MODEL;
1043
- };
1044
- var getOpenRouterModelKwargs = (apiEndpoint) => {
1045
- const endpoint = String(apiEndpoint ?? "").trim();
1046
- if (!endpoint) {
1047
- return {};
1048
- }
1049
- let hostname = "";
1050
- try {
1051
- hostname = new URL(endpoint).hostname;
1052
- } catch {
1053
- hostname = endpoint;
1054
- }
1055
- if (!hostname.toLowerCase().includes("openrouter")) {
1056
- return {};
1057
- }
1058
- return {
1059
- provider: {
1060
- ignore: ["azure"]
1061
- }
1062
- };
1063
- };
1064
- var getAiSettings = async (userName = "root") => {
1065
- let settingsPromise = userSettingsCache.get(userName);
1066
- if (!settingsPromise) {
1067
- settingsPromise = (0, import_userSettings.getUserSettings)(userName);
1068
- settingsPromise.catch(() => {
1069
- userSettingsCache.delete(userName);
1070
- });
1071
- userSettingsCache.set(userName, settingsPromise);
1072
- }
1073
- const settings = await settingsPromise;
1074
- if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
1075
- throw new Error(`AI settings are incomplete for user ${userName}`);
1076
- }
1077
- return settings;
1078
- };
1079
- var createAiModel = async (userName = "root", requestedModelName) => {
1080
- const settings = await getAiSettings(userName);
1081
- const modelName = resolveAiModelName(settings, requestedModelName);
1082
- const cacheKey = getAiModelCacheKey(userName, modelName);
1083
- let modelPromise = aiModelCache.get(cacheKey);
1084
- if (!modelPromise) {
1085
- modelPromise = (async () => {
1086
- const { ChatOpenAI } = await import("@langchain/openai");
1087
- const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
1088
- return new ChatOpenAI({
1089
- temperature: 0.2,
1090
- modelName,
1091
- apiKey: settings.AI_API_KEY,
1092
- ...Object.keys(modelKwargs).length ? { modelKwargs } : {},
1093
- configuration: {
1094
- baseURL: settings.AI_API_ENDPOINT,
1095
- defaultHeaders: {
1096
- "HTTP-Referer": "https://tradejs.dev",
1097
- "X-Title": "Inv"
1098
- }
1099
- }
1100
- });
1101
- })();
1102
- modelPromise.catch(() => {
1103
- aiModelCache.delete(cacheKey);
1104
- });
1105
- aiModelCache.set(cacheKey, modelPromise);
1106
- }
1107
- return modelPromise;
1108
- };
1109
- var getAiModel = async (userName = "root", requestedModelName) => {
1110
- const settings = await getAiSettings(userName);
1111
- const resolvedModelName = resolveAiModelName(settings, requestedModelName);
1112
- try {
1113
- return await createAiModel(userName, resolvedModelName);
1114
- } catch (error) {
1115
- aiModelCache.delete(getAiModelCacheKey(userName, resolvedModelName));
1116
- userSettingsCache.delete(userName);
1117
- throw error;
1118
- }
1119
- };
1120
- var ensureAiStrategyPluginsLoaded = async () => {
1121
- await ensureStrategyPluginsLoaded();
1122
- };
1123
- var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
1124
- if (options.signal) {
1125
- await ensureAiStrategyPluginsLoaded();
1126
- }
1127
- const [{ HumanMessage, SystemMessage }, model, settings] = await Promise.all([
1128
- import("@langchain/core/messages"),
1129
- getAiModel(options.userName, options.model),
1130
- getAiSettings(options.userName)
1131
- ]);
1132
- const messages = [];
1133
- const responseLanguage = (0, import_aiLanguages.getAiResponseLanguagePromptName)(
1134
- settings.AI_RESPONSE_LANGUAGE || import_aiLanguages.DEFAULT_AI_RESPONSE_LANGUAGE
1135
- );
1136
- messages.push(new SystemMessage(systemPrompt));
1137
- messages.push(
1138
- new SystemMessage(
1139
- `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
1140
- )
1141
- );
1142
- messages.push(
1143
- new HumanMessage({
1144
- content: [
1145
- {
1146
- type: "text",
1147
- text: humanPrompt
1148
- }
1149
- ]
1150
- })
1151
- );
1152
- let response;
1153
- try {
1154
- response = await model.invoke(messages);
1155
- } catch (error) {
1156
- throw getAiInvocationError(error);
1157
- }
1158
- const responseContent = normalizeResponseContent(response?.content);
1159
- if (isEmptyResponseContent(responseContent)) {
1160
- throw new Error("AI provider returned an empty chat completion");
1161
- }
1162
- const parsed = parseAIResponse(responseContent);
1163
- const normalized = normalizeAnalysis(parsed);
1164
- if (!options.signal) {
1165
- return normalized;
1166
- }
1167
- return postProcessAiAnalysisByStrategy(
1168
- options.signal,
1169
- normalized,
1170
- options.payload
1171
- );
1172
- };
1173
- var runAiPromptLocal = async (signal, options = {}) => {
1174
- await ensureAiStrategyPluginsLoaded();
1175
- const payload = options.payload ?? buildAiPayload(signal);
1176
- const gateContext = getDeterministicAiGateContext(payload);
1177
- const signalDirection = getSignalDirection(signal);
1178
- const deterministicQuality = getDeterministicQuality(gateContext);
1179
- const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
1180
- return postProcessLocalAiAnalysisByStrategy(
1181
- signal,
1182
- {
1183
- direction: approvalAllowedNow ? signalDirection : null,
1184
- quality: deterministicQuality,
1185
- needRetest: !approvalAllowedNow,
1186
- retestPrice: null,
1187
- takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
1188
- stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
1189
- },
1190
- payload
1191
- );
1192
- };
1193
- var askAI = async (signal, options = {}) => {
1194
- const { symbol } = signal;
1195
- await ensureAiStrategyPluginsLoaded();
1196
- const payload = buildAiPayload(signal);
1197
- const content = await runAiPrompt(
1198
- {
1199
- systemPrompt: buildAiSystemPrompt(signal),
1200
- humanPrompt: buildAiHumanPrompt(signal, payload)
1201
- },
1202
- {
1203
- ...options,
1204
- signal,
1205
- payload
1206
- }
1207
- );
1208
- await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
1209
- return content;
1210
- };
1211
-
1212
- // src/strategyAdapters/ml.ts
1213
- var defaultMlAdapter = {
1214
- normalizeStrategyConfig: (strategyConfig) => strategyConfig
1215
- };
1216
- var getStrategyMlAdapter = (strategy, profileId) => {
1217
- const strategyAdapter = getStrategyProfileMlAdapter(
1218
- getStrategyManifest(strategy),
1219
- profileId
1220
- );
1221
- if (!strategyAdapter) return defaultMlAdapter;
1222
- return {
1223
- ...defaultMlAdapter,
1224
- ...strategyAdapter
1225
- };
1226
- };
1227
-
1228
- // src/mlPayload.ts
1229
- var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
1230
- return getStrategyMlAdapter(
1231
- strategyName,
1232
- profileId
1233
- ).normalizeStrategyConfig?.(strategyConfig);
1234
- };
1235
- var buildMlPayload = (payload) => {
1236
- const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
1237
- const profileId = payload.signal?.policyProfileId;
1238
- const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
1239
- const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
1240
- const nextSignal = {
1241
- ...normalizedSignal,
1242
- indicators: {
1243
- ...normalizedSignal?.indicators ?? {}
1244
- }
1245
- };
1246
- const nextContext = payload.context ? {
1247
- ...payload.context,
1248
- strategyConfig: normalizeStrategyConfig(
1249
- payload.context.strategyConfig,
1250
- strategyName,
1251
- profileId
1252
- )
1253
- } : void 0;
1254
- return {
1255
- signal: nextSignal,
1256
- context: nextContext
1257
- };
1258
- };
1531
+ Rules for using trimmed series (last 5 values):
1532
+ - Do not make strong long-term conclusions from only 5 points.
1533
+ - Use 4h and 1d series as brief context, not full history.
1534
+ - If the data is too limited for confidence, reduce quality and use cautious wording.
1259
1535
 
1260
- // src/tradejsConfig.ts
1261
- var import_fs = __toESM(require("fs"));
1262
- var import_path = __toESM(require("path"));
1263
- var import_url = require("url");
1264
- var import_config = require("@tradejs/core/config");
1265
- var import_logger = require("@tradejs/infra/logger");
1266
- var CONFIG_FILE_NAMES = [
1267
- "tradejs.config.ts",
1268
- "tradejs.config.mts",
1269
- "tradejs.config.js",
1270
- "tradejs.config.mjs",
1271
- "tradejs.config.cjs"
1272
- ];
1273
- var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
1274
- var cachedByCwd = /* @__PURE__ */ new Map();
1275
- var announcedConfigFile = /* @__PURE__ */ new Set();
1276
- var tsNodeRegistered = false;
1277
- var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
1278
- var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
1279
- var getTradejsProjectCwd = (cwd) => {
1280
- const explicit = String(cwd ?? "").trim();
1281
- if (explicit) {
1282
- return import_path.default.resolve(explicit);
1283
- }
1284
- const fromEnv = String(process.env.PROJECT_CWD || "").trim();
1285
- if (fromEnv) {
1286
- return import_path.default.resolve(fromEnv);
1287
- }
1288
- return process.cwd();
1536
+ Short few-shot examples:
1537
+ {"direction":"LONG","quality":4,"needRetest":true,"retestPrice":100.2,"takeProfitPrice":101.5,"stopLossPrice":98.9,"setup":"Likely trendline breakout upward, but the signal still needs a level check for confirmation.","confirmations":"The coin shows momentum support without obvious overheating, but confirmation is not fully clean yet.","btcContext":"BTC is neutral-to-supportive and does not conflict with the current LONG signal.","retestPlan":"The key level is 100.2; holding above it would confirm the signal structure.","riskLevels":"The supplied TP and SL remain on the correct sides of the current price and still look internally coherent.","qualityReason":"Quality=4 because the structure is solid, but an extra level confirmation is still preferable.","triggerInvalidation":"The structure confirms on a hold above the level and weakens on a move back under the line."}
1538
+ {"direction":null,"quality":2,"needRetest":false,"retestPrice":null,"takeProfitPrice":null,"stopLossPrice":null,"setup":"Touch or noise around the trendline without a convincing breakout.","confirmations":"Indicators are mixed and do not provide strong structural support.","btcContext":"BTC is either conflicting or not supportive of the current thesis.","retestPlan":"It is too early to define an extra level because a quality breakout is not present yet.","riskLevels":"The supplied levels should not be treated as confirmed while the structure remains weak.","qualityReason":"Quality=2 because timing is weak and confirmations are limited.","triggerInvalidation":"Wait for a clear breakout and confirmation from both the coin and BTC."}
1539
+
1540
+ Return only the JSON object, with no extra characters.
1541
+ ${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
1542
+ `;
1543
+ var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
1544
+ var getDeterministicAiGateContext = (payload) => {
1545
+ const additionalIndicators = asRecord(payload.additionalIndicators);
1546
+ const candidates = [
1547
+ additionalIndicators,
1548
+ ...Object.values(additionalIndicators ?? {}).map(asRecord)
1549
+ ].filter((value) => Boolean(value));
1550
+ return candidates.find(
1551
+ (candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
1552
+ ) ?? null;
1289
1553
  };
1290
- var normalizeConfig = (rawConfig) => {
1291
- if (!rawConfig || typeof rawConfig !== "object") {
1292
- return {};
1293
- }
1294
- const config = rawConfig;
1295
- const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
1296
- const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
1297
- const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
1298
- const hooks = (0, import_config.normalizeTradejsConfigHooks)(
1299
- config.hooks
1554
+ var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
1555
+ Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
1556
+ This is a structure-classification and audit task, not execution advice. Determine whether the current structure confirms the existing signal, how structurally coherent it is right now, whether an extra confirmation level is needed, and whether the already supplied levels in \`payload.signal.prices\` still look internally coherent. Do not replace the original thesis with a new one and do not invent new levels; return only the requested JSON.
1557
+
1558
+ Trade payload:
1559
+ ${JSON.stringify(payload)}
1560
+ ${buildAiHumanPromptAddonByStrategy(signal, payload)}
1561
+ `;
1562
+ var getAiInvocationError = (error) => {
1563
+ const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
1564
+ const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
1565
+ details
1300
1566
  );
1301
- return {
1302
- strategies: strategies2,
1303
- indicators,
1304
- connectors,
1305
- ...hooks ? { hooks } : {}
1306
- };
1307
- };
1308
- var getNodeCreateRequire = () => {
1309
- const builtinModule = process.getBuiltinModule?.("module");
1310
- if (typeof builtinModule?.createRequire === "function") {
1311
- return builtinModule.createRequire;
1312
- }
1313
- throw new TypeError("module.createRequire is not available");
1314
- };
1315
- var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
1316
- var ensureTsNodeRegistered = async () => {
1317
- if (tsNodeRegistered) {
1318
- return;
1319
- }
1320
- const tsNodeModule = await import("ts-node");
1321
- const tsNode = tsNodeModule.default ?? tsNodeModule;
1322
- tsNode.register?.({
1323
- transpileOnly: true,
1324
- compilerOptions: {
1325
- module: "Node16",
1326
- moduleResolution: "node16"
1327
- }
1328
- });
1329
- tsNodeRegistered = true;
1330
- };
1331
- var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
1332
- const projectRoot = getTradejsProjectCwd(cwd);
1333
- if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
1334
- return;
1335
- }
1336
- const tsconfigPathsModule = await import("tsconfig-paths");
1337
- const loadConfig = tsconfigPathsModule.loadConfig;
1338
- const register = tsconfigPathsModule.register;
1339
- if (typeof loadConfig !== "function" || typeof register !== "function") {
1340
- return;
1341
- }
1342
- const loadedConfig = loadConfig(projectRoot);
1343
- if (loadedConfig.resultType !== "success") {
1344
- return;
1345
- }
1346
- register({
1347
- baseUrl: loadedConfig.absoluteBaseUrl,
1348
- paths: loadedConfig.paths,
1349
- addMatchAll: false
1350
- });
1351
- tsconfigPathsRegisteredByCwd.add(projectRoot);
1352
- };
1353
- var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
1354
- const projectRoot = getTradejsProjectCwd(cwd);
1355
- const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
1356
- if (cachedMatcher) {
1357
- const resolved2 = cachedMatcher(moduleName);
1358
- return resolved2 || null;
1359
- }
1360
- const tsconfigPathsModule = await import("tsconfig-paths");
1361
- const loadConfig = tsconfigPathsModule.loadConfig;
1362
- const createMatchPath = tsconfigPathsModule.createMatchPath;
1363
- if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
1364
- return null;
1365
- }
1366
- const loadedConfig = loadConfig(projectRoot);
1367
- if (loadedConfig.resultType !== "success") {
1368
- return null;
1369
- }
1370
- const matchPath = createMatchPath(
1371
- loadedConfig.absoluteBaseUrl,
1372
- loadedConfig.paths
1567
+ const wrapped = new Error(
1568
+ isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
1373
1569
  );
1374
- const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
1375
- ".ts",
1376
- ".tsx",
1377
- ".mts",
1378
- ".cts",
1379
- ".js",
1380
- ".jsx",
1381
- ".mjs",
1382
- ".cjs",
1383
- ".json"
1384
- ]) || "";
1385
- tsconfigPathMatchersByCwd.set(projectRoot, matcher);
1386
- const resolved = matcher(moduleName);
1387
- return resolved || null;
1388
- };
1389
- var toImportSpecifier = (moduleName) => {
1390
- if (moduleName.startsWith("file://")) {
1391
- return moduleName;
1392
- }
1393
- if (import_path.default.isAbsolute(moduleName)) {
1394
- return (0, import_url.pathToFileURL)(moduleName).href;
1395
- }
1396
- return moduleName;
1397
- };
1398
- var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
1399
- var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
1400
- var isBareModuleSpecifier = (moduleName) => {
1401
- const normalized = String(moduleName ?? "").trim();
1402
- if (!normalized) {
1403
- return false;
1404
- }
1405
- if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
1406
- return false;
1407
- }
1408
- return true;
1570
+ wrapped.cause = error;
1571
+ return wrapped;
1409
1572
  };
1410
- var importConfigFile = async (configFilePath) => {
1411
- const ext = import_path.default.extname(configFilePath).toLowerCase();
1412
- const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
1413
- if (ext === ".ts" || ext === ".mts") {
1414
- const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
1415
- await ensureTsNodeRegistered();
1416
- await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
1417
- return requireFn(configFilePath);
1573
+ var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
1574
+ var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
1575
+ var userSettingsCache = /* @__PURE__ */ new Map();
1576
+ var aiModelCache = /* @__PURE__ */ new Map();
1577
+ var getAiModelCacheKey = (userName, modelName) => `${userName}::${modelName}`;
1578
+ var resolveAiModelName = (settings, requestedModelName) => {
1579
+ const explicitModelName = typeof requestedModelName === "string" ? requestedModelName.trim() : "";
1580
+ if (explicitModelName) {
1581
+ return explicitModelName;
1418
1582
  }
1419
- return import(
1420
- /* webpackIgnore: true */
1421
- configFileUrl
1422
- );
1583
+ const settingsModelName = typeof settings.AI_MODEL === "string" ? settings.AI_MODEL.trim() : "";
1584
+ return settingsModelName || DEFAULT_AI_MODEL;
1423
1585
  };
1424
- var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
1425
- const normalized = String(moduleName ?? "").trim();
1426
- if (!normalized) {
1586
+ var getOpenRouterModelKwargs = (apiEndpoint) => {
1587
+ const endpoint = String(apiEndpoint ?? "").trim();
1588
+ if (!endpoint) {
1427
1589
  return {};
1428
1590
  }
1429
- let modulePath = normalized;
1430
- if (normalized.startsWith("file://")) {
1431
- try {
1432
- modulePath = (0, import_url.fileURLToPath)(normalized);
1433
- } catch {
1434
- modulePath = normalized;
1591
+ let hostname = "";
1592
+ try {
1593
+ hostname = new URL(endpoint).hostname;
1594
+ } catch {
1595
+ hostname = endpoint;
1596
+ }
1597
+ if (!hostname.toLowerCase().includes("openrouter")) {
1598
+ return {};
1599
+ }
1600
+ return {
1601
+ provider: {
1602
+ ignore: ["azure"]
1435
1603
  }
1604
+ };
1605
+ };
1606
+ var getAiSettings = async (userName = "root") => {
1607
+ let settingsPromise = userSettingsCache.get(userName);
1608
+ if (!settingsPromise) {
1609
+ settingsPromise = (0, import_userSettings.getUserSettings)(userName).then((settings2) => {
1610
+ const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
1611
+ return {
1612
+ ...settings2,
1613
+ AI_API_ENDPOINT: endpoint,
1614
+ AI_MODEL: (0, import_aiModels.normalizeAiModel)(settings2.AI_MODEL, endpoint),
1615
+ AI_RESPONSE_LANGUAGE: (0, import_aiLanguages.normalizeAiResponseLanguage)(
1616
+ settings2.AI_RESPONSE_LANGUAGE
1617
+ )
1618
+ };
1619
+ });
1620
+ settingsPromise.catch(() => {
1621
+ userSettingsCache.delete(userName);
1622
+ });
1623
+ userSettingsCache.set(userName, settingsPromise);
1436
1624
  }
1437
- const requireFn = getRequireFn(
1438
- import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
1439
- );
1440
- if (isTsModulePath(modulePath)) {
1441
- await ensureTsNodeRegistered();
1442
- await ensureTsconfigPathsRegistered(cwd);
1443
- return requireFn(modulePath);
1625
+ const settings = await settingsPromise;
1626
+ if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
1627
+ throw new Error(`AI settings are incomplete for user ${userName}`);
1444
1628
  }
1445
- if (isBareModuleSpecifier(normalized)) {
1446
- await ensureTsconfigPathsRegistered(cwd);
1447
- try {
1448
- return requireFn(normalized);
1449
- } catch (error) {
1450
- const resolvedByTsconfig = await resolveTsconfigPathModule(
1451
- normalized,
1452
- cwd
1453
- );
1454
- if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
1455
- return requireFn(resolvedByTsconfig);
1456
- }
1457
- throw error;
1458
- }
1629
+ return settings;
1630
+ };
1631
+ var createAiModel = async (userName = "root", requestedModelName) => {
1632
+ const settings = await getAiSettings(userName);
1633
+ const modelName = resolveAiModelName(settings, requestedModelName);
1634
+ const cacheKey = getAiModelCacheKey(userName, modelName);
1635
+ let modelPromise = aiModelCache.get(cacheKey);
1636
+ if (!modelPromise) {
1637
+ modelPromise = (async () => {
1638
+ const { ChatOpenAI } = await import("@langchain/openai");
1639
+ const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
1640
+ return new ChatOpenAI({
1641
+ temperature: 0.2,
1642
+ modelName,
1643
+ apiKey: settings.AI_API_KEY,
1644
+ ...Object.keys(modelKwargs).length ? { modelKwargs } : {},
1645
+ configuration: {
1646
+ baseURL: settings.AI_API_ENDPOINT,
1647
+ defaultHeaders: {
1648
+ "HTTP-Referer": "https://tradejs.dev",
1649
+ "X-Title": "Inv"
1650
+ }
1651
+ }
1652
+ });
1653
+ })();
1654
+ modelPromise.catch(() => {
1655
+ aiModelCache.delete(cacheKey);
1656
+ });
1657
+ aiModelCache.set(cacheKey, modelPromise);
1459
1658
  }
1659
+ return modelPromise;
1660
+ };
1661
+ var getAiModel = async (userName = "root", requestedModelName) => {
1662
+ const settings = await getAiSettings(userName);
1663
+ const resolvedModelName = resolveAiModelName(settings, requestedModelName);
1460
1664
  try {
1461
- return await import(
1462
- /* webpackIgnore: true */
1463
- toImportSpecifier(normalized)
1464
- );
1665
+ return await createAiModel(userName, resolvedModelName);
1465
1666
  } catch (error) {
1466
- if (isTsModulePath(modulePath)) {
1467
- await ensureTsNodeRegistered();
1468
- await ensureTsconfigPathsRegistered(cwd);
1469
- return requireFn(modulePath);
1470
- }
1667
+ aiModelCache.delete(getAiModelCacheKey(userName, resolvedModelName));
1668
+ userSettingsCache.delete(userName);
1471
1669
  throw error;
1472
1670
  }
1473
1671
  };
1474
- var resolveExportedConfig = (moduleExports) => {
1475
- const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
1476
- return normalizeConfig(candidate);
1477
- };
1478
- var findConfigFilePath = (cwd) => {
1479
- let currentDir = import_path.default.resolve(cwd);
1480
- while (true) {
1481
- for (const fileName of CONFIG_FILE_NAMES) {
1482
- const fullPath = import_path.default.join(currentDir, fileName);
1483
- if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
1484
- return fullPath;
1485
- }
1486
- }
1487
- const parentDir = import_path.default.dirname(currentDir);
1488
- if (parentDir === currentDir) {
1489
- return null;
1490
- }
1491
- currentDir = parentDir;
1492
- }
1493
- };
1494
- var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
1495
- const normalized = String(moduleName ?? "").trim();
1496
- if (!normalized) {
1497
- return "";
1672
+ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
1673
+ const [{ HumanMessage, SystemMessage }, model, settings] = await Promise.all([
1674
+ import("@langchain/core/messages"),
1675
+ getAiModel(options.userName, options.model),
1676
+ getAiSettings(options.userName)
1677
+ ]);
1678
+ const messages = [];
1679
+ const responseLanguage = (0, import_aiLanguages.getAiResponseLanguagePromptName)(
1680
+ settings.AI_RESPONSE_LANGUAGE || import_aiLanguages.DEFAULT_AI_RESPONSE_LANGUAGE
1681
+ );
1682
+ messages.push(new SystemMessage(systemPrompt));
1683
+ messages.push(
1684
+ new SystemMessage(
1685
+ `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
1686
+ )
1687
+ );
1688
+ messages.push(
1689
+ new HumanMessage({
1690
+ content: [
1691
+ {
1692
+ type: "text",
1693
+ text: humanPrompt
1694
+ }
1695
+ ]
1696
+ })
1697
+ );
1698
+ let response;
1699
+ try {
1700
+ response = await model.invoke(messages);
1701
+ } catch (error) {
1702
+ throw getAiInvocationError(error);
1498
1703
  }
1499
- if (normalized.startsWith("file://")) {
1500
- try {
1501
- return (0, import_url.fileURLToPath)(normalized);
1502
- } catch {
1503
- return normalized;
1504
- }
1704
+ const responseContent = normalizeResponseContent(response?.content);
1705
+ if (isEmptyResponseContent(responseContent)) {
1706
+ throw new Error("AI provider returned an empty chat completion");
1505
1707
  }
1506
- if (import_path.default.isAbsolute(normalized)) {
1708
+ const parsed = parseAIResponse(responseContent);
1709
+ const normalized = normalizeAnalysis(parsed);
1710
+ if (!options.signal) {
1507
1711
  return normalized;
1508
1712
  }
1509
- if (isRelativeModulePath(normalized)) {
1510
- return import_path.default.resolve(cwd, normalized);
1511
- }
1512
- return normalized;
1713
+ return postProcessAiAnalysisByStrategy(
1714
+ options.signal,
1715
+ normalized,
1716
+ options.payload
1717
+ );
1513
1718
  };
1514
- var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
1515
- const cached = cachedByCwd.get(cwd);
1516
- if (cached) {
1517
- return cached;
1518
- }
1519
- const configFilePath = findConfigFilePath(cwd);
1520
- if (!configFilePath) {
1521
- cachedByCwd.set(cwd, {});
1522
- return {};
1523
- }
1524
- try {
1525
- const moduleExports = await importConfigFile(configFilePath);
1526
- const config = resolveExportedConfig(moduleExports);
1527
- cachedByCwd.set(cwd, config);
1528
- if (!announcedConfigFile.has(configFilePath)) {
1529
- announcedConfigFile.add(configFilePath);
1530
- import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
1719
+ var runAiPromptLocal = async (signal, options = {}) => {
1720
+ const payload = options.payload ?? buildAiPayload(signal);
1721
+ const gateContext = getDeterministicAiGateContext(payload);
1722
+ const signalDirection = getSignalDirection(signal);
1723
+ const deterministicQuality = getDeterministicQuality(gateContext);
1724
+ const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
1725
+ return postProcessLocalAiAnalysisByStrategy(
1726
+ signal,
1727
+ {
1728
+ direction: approvalAllowedNow ? signalDirection : null,
1729
+ quality: deterministicQuality,
1730
+ needRetest: !approvalAllowedNow,
1731
+ retestPrice: null,
1732
+ takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
1733
+ stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
1734
+ },
1735
+ payload
1736
+ );
1737
+ };
1738
+ var askAI = async (signal, options = {}) => {
1739
+ const { symbol } = signal;
1740
+ const payload = buildAiPayload(signal);
1741
+ const content = await runAiPrompt(
1742
+ {
1743
+ systemPrompt: buildAiSystemPrompt(signal),
1744
+ humanPrompt: buildAiHumanPrompt(signal, payload)
1745
+ },
1746
+ {
1747
+ ...options,
1748
+ signal,
1749
+ payload
1531
1750
  }
1532
- return config;
1533
- } catch (error) {
1534
- import_logger.logger.log(
1535
- "warn",
1536
- "Failed to load TradeJS config from %s: %s",
1537
- configFilePath,
1538
- String(error)
1539
- );
1540
- cachedByCwd.set(cwd, {});
1541
- return {};
1542
- }
1751
+ );
1752
+ await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
1753
+ return content;
1754
+ };
1755
+
1756
+ // src/strategyAdapters/ml.ts
1757
+ var defaultMlAdapter = {
1758
+ normalizeStrategyConfig: (strategyConfig) => strategyConfig
1759
+ };
1760
+ var getStrategyMlAdapter = (strategy, profileId) => {
1761
+ const strategyAdapter = getStrategyProfileMlAdapter(
1762
+ getStrategyManifest(strategy),
1763
+ profileId
1764
+ );
1765
+ if (!strategyAdapter) return defaultMlAdapter;
1766
+ return {
1767
+ ...defaultMlAdapter,
1768
+ ...strategyAdapter
1769
+ };
1770
+ };
1771
+
1772
+ // src/mlPayload.ts
1773
+ var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
1774
+ return getStrategyMlAdapter(
1775
+ strategyName,
1776
+ profileId
1777
+ ).normalizeStrategyConfig?.(strategyConfig);
1778
+ };
1779
+ var buildMlPayload = (payload) => {
1780
+ const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
1781
+ const profileId = payload.signal?.policyProfileId;
1782
+ const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
1783
+ const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
1784
+ const nextSignal = {
1785
+ ...normalizedSignal,
1786
+ indicators: {
1787
+ ...normalizedSignal?.indicators ?? {}
1788
+ }
1789
+ };
1790
+ const nextContext = payload.context ? {
1791
+ ...payload.context,
1792
+ strategyConfig: normalizeStrategyConfig(
1793
+ payload.context.strategyConfig,
1794
+ strategyName,
1795
+ profileId
1796
+ )
1797
+ } : void 0;
1798
+ return {
1799
+ signal: nextSignal,
1800
+ context: nextContext
1801
+ };
1543
1802
  };
1544
1803
 
1545
1804
  // src/runtimeJournal.ts
@@ -1547,7 +1806,7 @@ var import_node_crypto = require("crypto");
1547
1806
  var import_constants = require("@tradejs/core/constants");
1548
1807
  var import_time = require("@tradejs/core/time");
1549
1808
  var import_trade = require("@tradejs/core/trade");
1550
- var import_logger2 = require("@tradejs/infra/logger");
1809
+ var import_logger3 = require("@tradejs/infra/logger");
1551
1810
  var import_redis2 = require("@tradejs/infra/redis");
1552
1811
  var now = () => Date.now();
1553
1812
  var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
@@ -1606,7 +1865,7 @@ var recordRuntimeTradeOpen = async (params) => {
1606
1865
  )
1607
1866
  ]);
1608
1867
  } catch (error) {
1609
- import_logger2.logger.error(
1868
+ import_logger3.logger.error(
1610
1869
  "runtime trade open journal failed: %s %s",
1611
1870
  record.symbol,
1612
1871
  error?.message || String(error)
@@ -1673,7 +1932,7 @@ var recordRuntimeTradeIncrease = async (params) => {
1673
1932
  )
1674
1933
  ]);
1675
1934
  } catch (error) {
1676
- import_logger2.logger.error(
1935
+ import_logger3.logger.error(
1677
1936
  "runtime trade increase journal failed: %s %s",
1678
1937
  symbol,
1679
1938
  error?.message || String(error)
@@ -1780,7 +2039,7 @@ var markRuntimeTradeClosed = async (params) => {
1780
2039
  )
1781
2040
  ]);
1782
2041
  } catch (error) {
1783
- import_logger2.logger.error(
2042
+ import_logger3.logger.error(
1784
2043
  "runtime trade close journal failed: %s %s",
1785
2044
  symbol,
1786
2045
  error?.message || String(error)
@@ -1790,11 +2049,11 @@ var markRuntimeTradeClosed = async (params) => {
1790
2049
  };
1791
2050
 
1792
2051
  // src/strategyHelpers/marketContextStages.ts
1793
- var import_logger7 = require("@tradejs/infra/logger");
2052
+ var import_logger8 = require("@tradejs/infra/logger");
1794
2053
 
1795
2054
  // src/strategyHelpers/binanceMarketContext.ts
1796
2055
  var import_marketContext = require("@tradejs/infra/timescale/marketContext");
1797
- var import_logger3 = require("@tradejs/infra/logger");
2056
+ var import_logger4 = require("@tradejs/infra/logger");
1798
2057
  var import_strategies = require("@tradejs/core/strategies");
1799
2058
 
1800
2059
  // src/binanceBreadthUniverses.ts
@@ -2390,7 +2649,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
2390
2649
  throw error;
2391
2650
  }
2392
2651
  binanceMarketContextUnavailable = true;
2393
- import_logger3.logger.warn(
2652
+ import_logger4.logger.warn(
2394
2653
  "Binance market context disabled after Timescale read failure: %s",
2395
2654
  String(error)
2396
2655
  );
@@ -2400,7 +2659,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
2400
2659
 
2401
2660
  // src/strategyHelpers/coinMarketCapContext.ts
2402
2661
  var import_strategies2 = require("@tradejs/core/strategies");
2403
- var import_logger4 = require("@tradejs/infra/logger");
2662
+ var import_logger5 = require("@tradejs/infra/logger");
2404
2663
  var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
2405
2664
  var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
2406
2665
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
@@ -2889,7 +3148,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
2889
3148
  throw error;
2890
3149
  }
2891
3150
  coinMarketCapContextUnavailable = true;
2892
- import_logger4.logger.warn(
3151
+ import_logger5.logger.warn(
2893
3152
  "CoinMarketCap context disabled after Timescale read failure: %s",
2894
3153
  String(error)
2895
3154
  );
@@ -2898,12 +3157,12 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
2898
3157
  };
2899
3158
 
2900
3159
  // src/strategyHelpers/derivativesContext.ts
2901
- var import_indicators = require("@tradejs/core/indicators");
3160
+ var import_indicators2 = require("@tradejs/core/indicators");
2902
3161
  var import_data = require("@tradejs/core/data");
2903
3162
  var import_strategies3 = require("@tradejs/core/strategies");
2904
3163
  var import_constants2 = require("@tradejs/core/constants");
2905
3164
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
2906
- var import_logger5 = require("@tradejs/infra/logger");
3165
+ var import_logger6 = require("@tradejs/infra/logger");
2907
3166
  var STORED_INTERVALS = ["15m", "1h"];
2908
3167
  var CONTEXT_INTERVALS = ["15m", "1h"];
2909
3168
  var DEFAULT_LOOKBACK_HOURS = 48;
@@ -2932,7 +3191,7 @@ var parseLookbackMs = () => {
2932
3191
  };
2933
3192
  var withHourlyFallbackRows = (rowsByInterval) => ({
2934
3193
  "15m": rowsByInterval["15m"] ?? [],
2935
- "1h": (0, import_indicators.buildCoinalyzeHourlyRowsWithFallback)({
3194
+ "1h": (0, import_indicators2.buildCoinalyzeHourlyRowsWithFallback)({
2936
3195
  rows15m: rowsByInterval["15m"],
2937
3196
  fallbackRows1h: rowsByInterval["1h"]
2938
3197
  })
@@ -3060,7 +3319,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
3060
3319
  const targetSymbol = normalizeSymbol(signal.symbol);
3061
3320
  const lookbackMs = parseLookbackMs();
3062
3321
  const decisionTimeMs = signal.timestamp + (0, import_data.intervalToMs)(signal.interval);
3063
- const derivativesEndMs = (0, import_indicators.getLastClosedDerivativesBarStartMs)(
3322
+ const derivativesEndMs = (0, import_indicators2.getLastClosedDerivativesBarStartMs)(
3064
3323
  decisionTimeMs,
3065
3324
  "15m"
3066
3325
  );
@@ -3075,7 +3334,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
3075
3334
  });
3076
3335
  return [
3077
3336
  symbol,
3078
- (0, import_indicators.buildDerivativesContext)({
3337
+ (0, import_indicators2.buildDerivativesContext)({
3079
3338
  symbol,
3080
3339
  direction: signal.direction,
3081
3340
  timestamp: derivativesEndMs,
@@ -3102,7 +3361,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
3102
3361
  lookbackMs,
3103
3362
  ...params.abortSignal ? { signal: params.abortSignal } : {}
3104
3363
  });
3105
- const context = (0, import_indicators.buildDerivativesContext)({
3364
+ const context = (0, import_indicators2.buildDerivativesContext)({
3106
3365
  symbol: targetSymbol,
3107
3366
  direction: signal.direction,
3108
3367
  timestamp: derivativesEndMs,
@@ -3135,7 +3394,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
3135
3394
  throw error;
3136
3395
  }
3137
3396
  derivativesContextUnavailable = true;
3138
- import_logger5.logger.warn(
3397
+ import_logger6.logger.warn(
3139
3398
  "Derivatives context disabled after Timescale read failure: %s",
3140
3399
  String(error)
3141
3400
  );
@@ -3147,7 +3406,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
3147
3406
  var import_data2 = require("@tradejs/core/data");
3148
3407
  var import_strategies4 = require("@tradejs/core/strategies");
3149
3408
  var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
3150
- var import_logger6 = require("@tradejs/infra/logger");
3409
+ var import_logger7 = require("@tradejs/infra/logger");
3151
3410
 
3152
3411
  // src/hyperliquidWhaleUniverse.ts
3153
3412
  var import_node_crypto3 = require("crypto");
@@ -3667,7 +3926,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
3667
3926
  throw error;
3668
3927
  }
3669
3928
  hyperliquidWhaleContextUnavailable = true;
3670
- import_logger6.logger.warn(
3929
+ import_logger7.logger.warn(
3671
3930
  "Hyperliquid whale context disabled after Timescale read failure: %s",
3672
3931
  String(error)
3673
3932
  );
@@ -3750,7 +4009,7 @@ var runMarketContextStage = async ({
3750
4009
  elapsedMs: Date.now() - startedAt
3751
4010
  };
3752
4011
  if (status === "timed_out") {
3753
- import_logger7.logger.warn(
4012
+ import_logger8.logger.warn(
3754
4013
  "Market context stage timed out: %s after %sms",
3755
4014
  stage,
3756
4015
  result.elapsedMs
@@ -3940,7 +4199,7 @@ var enrichSignalWithAi = async ({
3940
4199
  signal.aiAnalysis = analysis;
3941
4200
  return resolveAiQuality(analysis, direction);
3942
4201
  } catch (err) {
3943
- import_logger8.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
4202
+ import_logger9.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
3944
4203
  }
3945
4204
  return void 0;
3946
4205
  };
@@ -3975,7 +4234,7 @@ var getOrderArrivalSnapshot = async ({
3975
4234
  spreadBps
3976
4235
  };
3977
4236
  } catch (error) {
3978
- import_logger8.logger.warn(
4237
+ import_logger9.logger.warn(
3979
4238
  "runtime order arrival snapshot failed: %s %s",
3980
4239
  symbol,
3981
4240
  error?.message || String(error)
@@ -4768,7 +5027,7 @@ var canUseSharedReplayState = ({
4768
5027
  }) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
4769
5028
 
4770
5029
  // src/strategy/runtimeExecution.ts
4771
- var import_logger9 = require("@tradejs/infra/logger");
5030
+ var import_logger10 = require("@tradejs/infra/logger");
4772
5031
  var import_types = require("@tradejs/types");
4773
5032
  var buildExitOrderSignal = ({
4774
5033
  strategyName,
@@ -4820,7 +5079,7 @@ var handleExitDecision = async ({
4820
5079
  deploymentId: connector.deploymentId
4821
5080
  });
4822
5081
  if (!activeTrade) {
4823
- import_logger9.logger.warn(
5082
+ import_logger10.logger.warn(
4824
5083
  "[%s] blocked closePosition for untracked runtime position: %s",
4825
5084
  strategyName ?? "unknown",
4826
5085
  symbol
@@ -4828,7 +5087,7 @@ var handleExitDecision = async ({
4828
5087
  return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
4829
5088
  }
4830
5089
  if (!strategyName || activeTrade.strategy !== strategyName) {
4831
- import_logger9.logger.warn(
5090
+ import_logger10.logger.warn(
4832
5091
  "[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
4833
5092
  strategyName ?? "unknown",
4834
5093
  symbol,
@@ -4880,7 +5139,7 @@ var handleExitDecision = async ({
4880
5139
  exitType: closedTrade?.exitType ?? "exit"
4881
5140
  });
4882
5141
  } catch (notificationError) {
4883
- import_logger9.logger.error(
5142
+ import_logger10.logger.error(
4884
5143
  "runtime close notification error: %s %s",
4885
5144
  symbol,
4886
5145
  notificationError
@@ -4894,7 +5153,7 @@ var handleExitDecision = async ({
4894
5153
  decision,
4895
5154
  market
4896
5155
  });
4897
- import_logger9.logger.error("close order error: %s %s", symbol, err);
5156
+ import_logger10.logger.error("close order error: %s %s", symbol, err);
4898
5157
  return "ORDER_ERROR";
4899
5158
  }
4900
5159
  return decision.code;
@@ -4921,7 +5180,7 @@ var handleProtectDecision = async ({
4921
5180
  decision,
4922
5181
  market
4923
5182
  });
4924
- import_logger9.logger.error("protect position error: %s %s", symbol, err);
5183
+ import_logger10.logger.error("protect position error: %s %s", symbol, err);
4925
5184
  return "ORDER_ERROR";
4926
5185
  }
4927
5186
  return decision.code;
@@ -5084,9 +5343,9 @@ var executeEntryDecision = async ({
5084
5343
  market
5085
5344
  });
5086
5345
  if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
5087
- import_logger9.logger.warn("order warning: %s %s", symbol, err);
5346
+ import_logger10.logger.warn("order warning: %s %s", symbol, err);
5088
5347
  } else {
5089
- import_logger9.logger.error("order error: %s %s", symbol, err);
5348
+ import_logger10.logger.error("order error: %s %s", symbol, err);
5090
5349
  }
5091
5350
  return signal ?? "ORDER_ERROR";
5092
5351
  }
@@ -5238,7 +5497,7 @@ var createStrategyRuntime = ({
5238
5497
  try {
5239
5498
  await projectHook(errorParams);
5240
5499
  } catch (hookError) {
5241
- import_logger10.logger.error(
5500
+ import_logger11.logger.error(
5242
5501
  "project hook onRuntimeError failed: %s %s",
5243
5502
  strategyName,
5244
5503
  hookError
@@ -5252,7 +5511,7 @@ var createStrategyRuntime = ({
5252
5511
  try {
5253
5512
  await onRuntimeError(errorParams);
5254
5513
  } catch (hookError) {
5255
- import_logger10.logger.error(
5514
+ import_logger11.logger.error(
5256
5515
  "runtime hook onRuntimeError failed: %s %s",
5257
5516
  strategyName,
5258
5517
  hookError
@@ -5266,7 +5525,7 @@ var createStrategyRuntime = ({
5266
5525
  try {
5267
5526
  return await hook(params);
5268
5527
  } catch (error) {
5269
- import_logger10.logger.error(
5528
+ import_logger11.logger.error(
5270
5529
  'strategy hook "%s" failed for %s: %s',
5271
5530
  stage,
5272
5531
  strategyName,
@@ -6006,237 +6265,8 @@ var createStrategyRuntime = ({
6006
6265
  return creator;
6007
6266
  };
6008
6267
 
6009
- // src/strategy/manifests.ts
6010
- var createStrategyRegistryState = () => ({
6011
- strategyCreators: /* @__PURE__ */ new Map(),
6012
- strategyManifestsMap: /* @__PURE__ */ new Map(),
6013
- pluginsLoadPromise: null
6014
- });
6015
- var registryStateByProjectRoot = /* @__PURE__ */ new Map();
6016
- var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
6017
- const projectRoot = getTradejsProjectCwd(cwd);
6018
- let state = registryStateByProjectRoot.get(projectRoot);
6019
- if (!state) {
6020
- state = createStrategyRegistryState();
6021
- registryStateByProjectRoot.set(projectRoot, state);
6022
- }
6023
- return {
6024
- projectRoot,
6025
- state
6026
- };
6027
- };
6028
- var toUniqueModules = (modules = []) => [
6029
- ...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
6030
- ];
6031
- var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
6032
- const config = await loadTradejsConfig(cwd);
6033
- return {
6034
- strategyModules: toUniqueModules(config.strategies),
6035
- indicatorModules: toUniqueModules(config.indicators)
6036
- };
6037
- };
6038
- var extractModuleEntries = (moduleExport, key) => {
6039
- if (!moduleExport || typeof moduleExport !== "object") {
6040
- return null;
6041
- }
6042
- const candidate = moduleExport;
6043
- if (Array.isArray(candidate[key])) {
6044
- return candidate[key];
6045
- }
6046
- const defaultExport = candidate.default;
6047
- if (defaultExport && Array.isArray(defaultExport[key])) {
6048
- return defaultExport[key];
6049
- }
6050
- return null;
6051
- };
6052
- var extractStrategyPluginDefinition = (moduleExport) => {
6053
- const strategyEntries = extractModuleEntries(
6054
- moduleExport,
6055
- "strategyEntries"
6056
- );
6057
- return strategyEntries ? { strategyEntries } : null;
6058
- };
6059
- var extractIndicatorPluginDefinition = (moduleExport) => {
6060
- const indicatorEntries = extractModuleEntries(
6061
- moduleExport,
6062
- "indicatorEntries"
6063
- );
6064
- return indicatorEntries ? { indicatorEntries } : null;
6065
- };
6066
- var registerEntries = (entries, source, state) => {
6067
- for (const entry of entries) {
6068
- const strategyName = entry.manifest?.name;
6069
- if (!strategyName) {
6070
- import_logger11.logger.warn("Skip strategy entry without name from %s", source);
6071
- continue;
6072
- }
6073
- if (state.strategyCreators.has(strategyName)) {
6074
- import_logger11.logger.warn(
6075
- 'Skip duplicate strategy "%s" from %s: already registered',
6076
- strategyName,
6077
- source
6078
- );
6079
- continue;
6080
- }
6081
- state.strategyManifestsMap.set(strategyName, entry.manifest);
6082
- state.strategyCreators.set(
6083
- strategyName,
6084
- createStrategyRuntime({
6085
- strategyName,
6086
- defaults: entry.defaults,
6087
- createCore: entry.createCore,
6088
- manifest: entry.manifest,
6089
- detectorKey: entry.detectorKey,
6090
- detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
6091
- resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
6092
- })
6093
- );
6094
- }
6095
- };
6096
- var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
6097
- if (typeof importTradejsModule === "function") {
6098
- return importTradejsModule(moduleName, cwd);
6099
- }
6100
- return import(
6101
- /* webpackIgnore: true */
6102
- moduleName
6103
- );
6104
- };
6105
- var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
6106
- const { projectRoot, state } = getStrategyRegistryState(cwd);
6107
- if (!state.pluginsLoadPromise) {
6108
- (0, import_indicators2.resetIndicatorRegistryCache)(projectRoot);
6109
- state.pluginsLoadPromise = (async () => {
6110
- const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
6111
- const strategySet = new Set(strategyModules);
6112
- const indicatorSet = new Set(indicatorModules);
6113
- const pluginModuleNames = [
6114
- .../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
6115
- ];
6116
- if (!pluginModuleNames.length) {
6117
- return;
6118
- }
6119
- for (const moduleName of pluginModuleNames) {
6120
- try {
6121
- const resolvedModuleName = resolvePluginModuleSpecifier(
6122
- moduleName,
6123
- projectRoot
6124
- );
6125
- const moduleExport = await importStrategyPluginModule(
6126
- resolvedModuleName,
6127
- projectRoot
6128
- );
6129
- if (strategySet.has(moduleName)) {
6130
- const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
6131
- if (!pluginDefinition) {
6132
- import_logger11.logger.warn(
6133
- 'Skip strategy plugin "%s": export { strategyEntries } is missing',
6134
- moduleName
6135
- );
6136
- } else {
6137
- registerEntries(
6138
- pluginDefinition.strategyEntries,
6139
- moduleName,
6140
- state
6141
- );
6142
- }
6143
- }
6144
- if (indicatorSet.has(moduleName)) {
6145
- const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
6146
- if (!indicatorPluginDefinition) {
6147
- import_logger11.logger.warn(
6148
- 'Skip indicator plugin "%s": export { indicatorEntries } is missing',
6149
- moduleName
6150
- );
6151
- } else {
6152
- (0, import_indicators2.registerIndicatorEntries)(
6153
- indicatorPluginDefinition.indicatorEntries,
6154
- moduleName,
6155
- projectRoot
6156
- );
6157
- }
6158
- }
6159
- if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
6160
- import_logger11.logger.warn(
6161
- 'Skip plugin "%s": no strategy/indicator sections requested in config',
6162
- moduleName
6163
- );
6164
- }
6165
- } catch (error) {
6166
- import_logger11.logger.warn(
6167
- 'Failed to load plugin "%s": %s',
6168
- moduleName,
6169
- String(error)
6170
- );
6171
- }
6172
- }
6173
- })();
6174
- }
6175
- await state.pluginsLoadPromise;
6176
- };
6177
- var ensureIndicatorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => ensureStrategyPluginsLoaded(cwd);
6178
- var getStrategyCreator = async (name, cwd = getTradejsProjectCwd()) => {
6179
- await ensureStrategyPluginsLoaded(cwd);
6180
- const { state } = getStrategyRegistryState(cwd);
6181
- return state.strategyCreators.get(name);
6182
- };
6183
- var getAvailableStrategyNames = async (cwd = getTradejsProjectCwd()) => {
6184
- await ensureStrategyPluginsLoaded(cwd);
6185
- const { state } = getStrategyRegistryState(cwd);
6186
- return [...state.strategyCreators.keys()].sort((a, b) => a.localeCompare(b));
6187
- };
6188
- var getRegisteredStrategies = (cwd = getTradejsProjectCwd()) => {
6189
- const { state } = getStrategyRegistryState(cwd);
6190
- return Object.fromEntries(state.strategyCreators.entries());
6191
- };
6192
- var getRegisteredManifests = (cwd = getTradejsProjectCwd()) => {
6193
- const { state } = getStrategyRegistryState(cwd);
6194
- return [...state.strategyManifestsMap.values()];
6195
- };
6196
- var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
6197
- if (!name) {
6198
- return void 0;
6199
- }
6200
- const { state } = getStrategyRegistryState(cwd);
6201
- return state.strategyManifestsMap.get(name);
6202
- };
6203
- var isKnownStrategy = (name, cwd = getTradejsProjectCwd()) => {
6204
- const { state } = getStrategyRegistryState(cwd);
6205
- return state.strategyCreators.has(name);
6206
- };
6207
- var registerStrategyEntries = (entries, cwd = getTradejsProjectCwd()) => {
6208
- const { state } = getStrategyRegistryState(cwd);
6209
- registerEntries(entries, "runtime", state);
6210
- };
6211
- var resetStrategyRegistryCache = (cwd) => {
6212
- const normalizedCwd = String(cwd ?? "").trim();
6213
- if (!normalizedCwd) {
6214
- registryStateByProjectRoot.clear();
6215
- (0, import_indicators2.resetIndicatorRegistryCache)();
6216
- return;
6217
- }
6218
- const projectRoot = getTradejsProjectCwd(normalizedCwd);
6219
- registryStateByProjectRoot.delete(projectRoot);
6220
- (0, import_indicators2.resetIndicatorRegistryCache)(projectRoot);
6221
- };
6222
- var strategies = new Proxy(
6223
- {},
6224
- {
6225
- get: (_target, property) => {
6226
- if (typeof property !== "string") {
6227
- return void 0;
6228
- }
6229
- return getStrategyRegistryState().state.strategyCreators.get(property);
6230
- },
6231
- ownKeys: () => {
6232
- return [...getStrategyRegistryState().state.strategyCreators.keys()];
6233
- },
6234
- getOwnPropertyDescriptor: () => ({
6235
- enumerable: true,
6236
- configurable: true
6237
- })
6238
- }
6239
- );
6268
+ // src/strategy/index.ts
6269
+ setStrategyRuntimeFactory(createStrategyRuntime);
6240
6270
  // Annotate the CommonJS export names for ESM import in node:
6241
6271
  0 && (module.exports = {
6242
6272
  ensureIndicatorPluginsLoaded,