@tradejs/node 2.0.21 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.d.mts +1 -2
- package/dist/ai.d.ts +1 -2
- package/dist/ai.js +163 -5157
- package/dist/ai.mjs +1 -3
- package/dist/backtest.js +1085 -1051
- package/dist/backtest.mjs +9 -3
- package/dist/{chunk-4AVFJMQL.mjs → chunk-OGAWBO3Z.mjs} +1722 -3147
- package/dist/chunk-VRXU4E4O.mjs +1469 -0
- package/dist/cli.js +228 -4847
- package/dist/cli.mjs +5 -2
- package/dist/registry.js +1007 -977
- package/dist/registry.mjs +2 -2
- package/dist/strategies.d.mts +1 -1
- package/dist/strategies.d.ts +1 -1
- package/dist/strategies.js +5139 -5111
- package/dist/strategies.mjs +22 -23
- package/package.json +5 -5
- package/dist/chunk-5YNMSWL3.mjs +0 -0
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
|
|
50
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
55
51
|
|
|
56
52
|
// src/strategyHelpers/runtime.ts
|
|
57
|
-
var
|
|
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/
|
|
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/
|
|
639
|
-
var
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
|
|
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
|
|
701
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
-
|
|
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
|
|
817
|
-
|
|
818
|
-
|
|
711
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
712
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
713
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
714
|
+
return;
|
|
819
715
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
|
829
|
-
if (
|
|
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
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
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
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
|
|
1302
|
-
|
|
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
|
-
|
|
1375
|
-
|
|
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
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
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
|
-
|
|
1420
|
-
|
|
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
|
|
1425
|
-
const
|
|
1426
|
-
if (!
|
|
1586
|
+
var getOpenRouterModelKwargs = (apiEndpoint) => {
|
|
1587
|
+
const endpoint = String(apiEndpoint ?? "").trim();
|
|
1588
|
+
if (!endpoint) {
|
|
1427
1589
|
return {};
|
|
1428
1590
|
}
|
|
1429
|
-
let
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
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
|
|
1438
|
-
|
|
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
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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
|
|
1462
|
-
/* webpackIgnore: true */
|
|
1463
|
-
toImportSpecifier(normalized)
|
|
1464
|
-
);
|
|
1665
|
+
return await createAiModel(userName, resolvedModelName);
|
|
1465
1666
|
} catch (error) {
|
|
1466
|
-
|
|
1467
|
-
|
|
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
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
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
|
-
|
|
1708
|
+
const parsed = parseAIResponse(responseContent);
|
|
1709
|
+
const normalized = normalizeAnalysis(parsed);
|
|
1710
|
+
if (!options.signal) {
|
|
1507
1711
|
return normalized;
|
|
1508
1712
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1713
|
+
return postProcessAiAnalysisByStrategy(
|
|
1714
|
+
options.signal,
|
|
1715
|
+
normalized,
|
|
1716
|
+
options.payload
|
|
1717
|
+
);
|
|
1513
1718
|
};
|
|
1514
|
-
var
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5346
|
+
import_logger10.logger.warn("order warning: %s %s", symbol, err);
|
|
5088
5347
|
} else {
|
|
5089
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
6010
|
-
|
|
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,
|