@tradejs/node 2.0.20 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/backtest.js
CHANGED
|
@@ -49,22 +49,20 @@ var import_strategies7 = require("@tradejs/core/strategies");
|
|
|
49
49
|
var import_time2 = require("@tradejs/core/time");
|
|
50
50
|
var import_logger13 = require("@tradejs/infra/logger");
|
|
51
51
|
|
|
52
|
-
// src/strategy/manifests.ts
|
|
53
|
-
var import_indicators2 = require("@tradejs/core/indicators");
|
|
54
|
-
var import_logger11 = require("@tradejs/infra/logger");
|
|
55
|
-
|
|
56
52
|
// src/strategyRuntime.ts
|
|
57
53
|
var import_constants5 = require("@tradejs/core/constants");
|
|
58
54
|
var import_strategies6 = require("@tradejs/core/strategies");
|
|
59
|
-
var
|
|
55
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
60
56
|
|
|
61
57
|
// src/strategyHelpers/runtime.ts
|
|
62
|
-
var
|
|
58
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
63
59
|
var import_constants3 = require("@tradejs/core/constants");
|
|
64
60
|
var import_ml2 = require("@tradejs/infra/ml");
|
|
65
61
|
|
|
66
62
|
// src/ai.ts
|
|
67
|
-
var import_aiLanguages = require("@tradejs/
|
|
63
|
+
var import_aiLanguages = require("@tradejs/core/aiLanguages");
|
|
64
|
+
var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
|
|
65
|
+
var import_aiModels = require("@tradejs/core/aiModels");
|
|
68
66
|
var import_redis = require("@tradejs/infra/redis");
|
|
69
67
|
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
70
68
|
|
|
@@ -640,911 +638,1139 @@ var buildAiMarketContext = (signal) => ({
|
|
|
640
638
|
}
|
|
641
639
|
});
|
|
642
640
|
|
|
643
|
-
// src/strategy/
|
|
644
|
-
var
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return {
|
|
671
|
-
id: inferredId,
|
|
672
|
-
appliesTo: { universes: [inferredId] },
|
|
673
|
-
marketDataRequirements: inferredId === "crypto" ? ["crypto.btcReference"] : [],
|
|
674
|
-
...manifest?.mlAdapter ? {
|
|
675
|
-
entryRuntimeDefaults: {
|
|
676
|
-
ml: {
|
|
677
|
-
modelKey: inferredId === "crypto" ? manifest.name : `${manifest.name}:tradfi`
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
} : {}
|
|
681
|
-
};
|
|
641
|
+
// src/strategy/manifests.ts
|
|
642
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
643
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
644
|
+
|
|
645
|
+
// src/tradejsConfig.ts
|
|
646
|
+
var import_fs = __toESM(require("fs"));
|
|
647
|
+
var import_path = __toESM(require("path"));
|
|
648
|
+
var import_url = require("url");
|
|
649
|
+
var import_config = require("@tradejs/core/config");
|
|
650
|
+
var import_logger = require("@tradejs/infra/logger");
|
|
651
|
+
var CONFIG_FILE_NAMES = [
|
|
652
|
+
"tradejs.config.ts",
|
|
653
|
+
"tradejs.config.mts",
|
|
654
|
+
"tradejs.config.js",
|
|
655
|
+
"tradejs.config.mjs",
|
|
656
|
+
"tradejs.config.cjs"
|
|
657
|
+
];
|
|
658
|
+
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
659
|
+
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
660
|
+
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
661
|
+
var tsNodeRegistered = false;
|
|
662
|
+
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
663
|
+
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
664
|
+
var getTradejsProjectCwd = (cwd) => {
|
|
665
|
+
const explicit = String(cwd ?? "").trim();
|
|
666
|
+
if (explicit) {
|
|
667
|
+
return import_path.default.resolve(explicit);
|
|
682
668
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
throw new Error(
|
|
687
|
-
`Unknown policy profile "${params.profileId}" for strategy "${manifest?.name}"`
|
|
688
|
-
);
|
|
689
|
-
}
|
|
690
|
-
if (!profileMatches(profile, params.universe, params.assetClass)) {
|
|
691
|
-
throw new Error(
|
|
692
|
-
`Policy profile "${params.profileId}" is not compatible with ${params.universe ?? "unknown"}:${params.assetClass ?? "unknown"}`
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
return profile;
|
|
669
|
+
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
670
|
+
if (fromEnv) {
|
|
671
|
+
return import_path.default.resolve(fromEnv);
|
|
696
672
|
}
|
|
697
|
-
|
|
698
|
-
(profile) => profileMatches(profile, params.universe, params.assetClass)
|
|
699
|
-
);
|
|
700
|
-
const defaultProfile = matching.find(
|
|
701
|
-
({ id }) => id === manifest?.defaultPolicyProfileId
|
|
702
|
-
);
|
|
703
|
-
return defaultProfile ?? matching[0];
|
|
673
|
+
return process.cwd();
|
|
704
674
|
};
|
|
705
|
-
var
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
// src/strategyAdapters/ai.ts
|
|
709
|
-
var toRecord2 = (value) => {
|
|
710
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
675
|
+
var normalizeConfig = (rawConfig) => {
|
|
676
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
711
677
|
return {};
|
|
712
678
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
679
|
+
const config = rawConfig;
|
|
680
|
+
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
681
|
+
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
682
|
+
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
683
|
+
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
684
|
+
config.hooks
|
|
685
|
+
);
|
|
720
686
|
return {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
direction: signal.direction,
|
|
726
|
-
timestamp: signal.timestamp,
|
|
727
|
-
strategy: signal.strategy,
|
|
728
|
-
prices: {
|
|
729
|
-
currentPrice: signal.prices.currentPrice,
|
|
730
|
-
takeProfitPrice: signal.prices.takeProfitPrice,
|
|
731
|
-
stopLossPrice: signal.prices.stopLossPrice
|
|
732
|
-
}
|
|
733
|
-
},
|
|
734
|
-
figures: trimSeriesDeep(signal.figures ?? {}),
|
|
735
|
-
indicators: buildCompactAiIndicatorsSnapshot(signal.indicators),
|
|
736
|
-
additionalIndicators: trimSeriesDeep(additionalIndicators)
|
|
687
|
+
strategies: strategies2,
|
|
688
|
+
indicators,
|
|
689
|
+
connectors,
|
|
690
|
+
...hooks ? { hooks } : {}
|
|
737
691
|
};
|
|
738
692
|
};
|
|
739
|
-
var
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const basePayload = buildBaseAiPayload(signal);
|
|
744
|
-
const adapter = getSignalAiAdapter(signal);
|
|
745
|
-
return adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
746
|
-
};
|
|
747
|
-
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
748
|
-
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
749
|
-
signal,
|
|
750
|
-
payload
|
|
751
|
-
}) ?? "";
|
|
752
|
-
var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => getSignalAiAdapter(signal).postProcessAnalysis?.({
|
|
753
|
-
signal,
|
|
754
|
-
payload,
|
|
755
|
-
analysis
|
|
756
|
-
}) ?? analysis;
|
|
757
|
-
var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => {
|
|
758
|
-
const adapter = getSignalAiAdapter(signal);
|
|
759
|
-
const strategyAnalysis = adapter.postProcessAnalysis?.({ signal, payload, analysis }) ?? analysis;
|
|
760
|
-
return adapter.postProcessLocalAnalysis?.({
|
|
761
|
-
signal,
|
|
762
|
-
payload,
|
|
763
|
-
analysis: strategyAnalysis
|
|
764
|
-
}) ?? strategyAnalysis;
|
|
765
|
-
};
|
|
766
|
-
|
|
767
|
-
// src/ai.ts
|
|
768
|
-
var parseAIResponse = (input) => {
|
|
769
|
-
try {
|
|
770
|
-
if (typeof input === "object" && input !== null) return input;
|
|
771
|
-
const match = input.match(/\{[\s\S]*\}/);
|
|
772
|
-
if (!match) throw new Error("JSON block not found");
|
|
773
|
-
return JSON.parse(match[0]);
|
|
774
|
-
} catch (err) {
|
|
775
|
-
console.error("Failed to parse AI response:", err);
|
|
776
|
-
console.log("Raw AI response:", input);
|
|
777
|
-
return {};
|
|
693
|
+
var getNodeCreateRequire = () => {
|
|
694
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
695
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
696
|
+
return builtinModule.createRequire;
|
|
778
697
|
}
|
|
698
|
+
throw new TypeError("module.createRequire is not available");
|
|
779
699
|
};
|
|
780
|
-
var
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
if (Array.isArray(content)) {
|
|
787
|
-
const text = content.map((part) => typeof part?.text === "string" ? part.text : "").join("\n").trim();
|
|
788
|
-
return text;
|
|
700
|
+
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
701
|
+
var ensureTsNodeRegistered = async () => {
|
|
702
|
+
if (tsNodeRegistered) {
|
|
703
|
+
return;
|
|
789
704
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
if (typeof value === "string" && value.trim()) {
|
|
798
|
-
const parsed = Number(value);
|
|
799
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
705
|
+
const tsNodeModule = await import("ts-node");
|
|
706
|
+
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
707
|
+
tsNode.register?.({
|
|
708
|
+
transpileOnly: true,
|
|
709
|
+
compilerOptions: {
|
|
710
|
+
module: "Node16",
|
|
711
|
+
moduleResolution: "node16"
|
|
800
712
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
const toText = (value) => typeof value === "string" ? value.slice(0, 400) : void 0;
|
|
804
|
-
return {
|
|
805
|
-
direction,
|
|
806
|
-
quality: qualityNum,
|
|
807
|
-
needRetest: Boolean(raw?.needRetest),
|
|
808
|
-
retestPrice: toNumberOrNull(raw?.retestPrice),
|
|
809
|
-
takeProfitPrice: toNumberOrNull(raw?.takeProfitPrice),
|
|
810
|
-
stopLossPrice: toNumberOrNull(raw?.stopLossPrice),
|
|
811
|
-
setup: toText(raw?.setup),
|
|
812
|
-
confirmations: toText(raw?.confirmations),
|
|
813
|
-
btcContext: toText(raw?.btcContext),
|
|
814
|
-
retestPlan: toText(raw?.retestPlan),
|
|
815
|
-
riskLevels: toText(raw?.riskLevels),
|
|
816
|
-
qualityReason: toText(raw?.qualityReason),
|
|
817
|
-
triggerInvalidation: toText(raw?.triggerInvalidation),
|
|
818
|
-
comment: typeof raw?.comment === "string" ? raw.comment.slice(0, 1024) : ""
|
|
819
|
-
};
|
|
713
|
+
});
|
|
714
|
+
tsNodeRegistered = true;
|
|
820
715
|
};
|
|
821
|
-
var
|
|
822
|
-
|
|
823
|
-
|
|
716
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
717
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
718
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
719
|
+
return;
|
|
824
720
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if (Number.isFinite(deterministicQuality)) {
|
|
831
|
-
return Math.max(1, Math.min(5, Math.round(deterministicQuality)));
|
|
721
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
722
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
723
|
+
const register = tsconfigPathsModule.register;
|
|
724
|
+
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
725
|
+
return;
|
|
832
726
|
}
|
|
833
|
-
const
|
|
834
|
-
if (
|
|
835
|
-
return
|
|
727
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
728
|
+
if (loadedConfig.resultType !== "success") {
|
|
729
|
+
return;
|
|
836
730
|
}
|
|
837
|
-
|
|
731
|
+
register({
|
|
732
|
+
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
733
|
+
paths: loadedConfig.paths,
|
|
734
|
+
addMatchAll: false
|
|
735
|
+
});
|
|
736
|
+
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
838
737
|
};
|
|
839
|
-
var
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
"
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
- All numbers must be finite, with no \`NaN\` or \`Infinity\`.
|
|
875
|
-
- All text fields must be short strings with no line breaks and no markdown lists.
|
|
876
|
-
- \`direction\` is not a new trade idea. It is only a compatibility flag for the existing signal: either exactly \`payload.signal.direction\` or \`null\` if the current structure does not confirm that signal. Never propose the opposite direction.
|
|
877
|
-
- \`quality\` is the structural confirmation level of the current signal right now, including timing and confirmations. It is not a general attractiveness score and not investment advice.
|
|
878
|
-
- \`needRetest\` indicates whether an additional confirmation level is required before the current signal can be treated as structurally confirmed.
|
|
879
|
-
- \`retestPrice\` is the key level that would confirm or invalidate the structure, or \`null\` if no extra level is needed or available.
|
|
880
|
-
- \`takeProfitPrice\` and \`stopLossPrice\` must not be newly invented levels. If the levels already supplied in \`payload.signal.prices\` still look internally coherent relative to the current price and the confirmed signal, you may return them as an audit of existing levels; otherwise return \`null\`.
|
|
881
|
-
- Use these fields as separate parts of the analysis:
|
|
882
|
-
- \`setup\`: the current structural setup or trendline state.
|
|
883
|
-
- \`confirmations\`: 2-4 concrete confirmations or conflicts from the coin indicators.
|
|
884
|
-
- \`btcContext\`: whether BTC supports the idea, is neutral, or conflicts with it.
|
|
885
|
-
- \`retestPlan\`: what must happen at the key level to confirm the structure, or why no extra level is needed.
|
|
886
|
-
- \`riskLevels\`: a short note on whether the existing levels and risk structure are internally coherent, without creating a new trade plan.
|
|
887
|
-
- \`qualityReason\`: why the quality score is what it is.
|
|
888
|
-
- \`triggerInvalidation\`: what must happen to confirm the signal or what invalidates the current structural thesis.
|
|
889
|
-
- \`comment\` is optional. If you include it, do not just duplicate the structured fields.
|
|
890
|
-
|
|
891
|
-
If the data is insufficient or the setup is weak, return \`"direction": null\`, \`quality <= 2\`, and explain why.
|
|
892
|
-
|
|
893
|
-
Input payload structure:
|
|
894
|
-
- payload.signal:
|
|
895
|
-
symbol, signalId, interval, direction, timestamp, strategy, prices
|
|
896
|
-
- payload.signal.prices:
|
|
897
|
-
currentPrice, takeProfitPrice, stopLossPrice
|
|
898
|
-
- payload.figures:
|
|
899
|
-
strategy-specific figures or geometry when available. Fields vary by strategy.
|
|
900
|
-
- payload.indicators:
|
|
901
|
-
historical indicator dictionaries and series for the coin and BTC; all series are already trimmed to the latest 5 values. Treat this block as recent-history transport, not as the primary source of the current shared context.
|
|
902
|
-
- payload.additionalIndicators:
|
|
903
|
-
strategy-specific summary/context fields plus the canonical current shared context snapshot.
|
|
904
|
-
This is not noise; it contains derived fields deliberately passed by the strategy to help the decision.
|
|
905
|
-
Examples: baseContext, helperFlags, structureContext, volatilitySummary.
|
|
906
|
-
Always inspect \`payload.additionalIndicators.baseContext\` first for the current shared state:
|
|
907
|
-
\u2022 \`baseContext.raw\`: current MA, ATR, BB, OBV, price stats, levels, BTC correlation.
|
|
908
|
-
\u2022 \`baseContext.regime\`: derived trend / volatility / momentum / session regime fields.
|
|
909
|
-
\u2022 \`baseContext.structure\`: local range position, breakout freshness/quality, level-touch counts, rejection wick context.
|
|
910
|
-
\u2022 \`baseContext.participation\`: volume/turnover participation, effort-vs-result context, Binance aggTrades, and fingerprinted Hyperliquid whale-flow when available.
|
|
911
|
-
\u2022 \`baseContext.relative\`: BTC/ETH relative-strength, benchmark MA bias context, Binance alt-basket breadth, and CoinMarketCap historical global/exchange/index context when available.
|
|
912
|
-
\u2022 \`baseContext.derivatives\`: Coinalyze-aligned derivatives summary when available.
|
|
913
|
-
\u2022 \`baseContext.mtf\`: compact multi-timeframe summary plus only the latest few candles for each timeframe.
|
|
914
|
-
\u2022 \`baseContext.gateFeatures\`: compact direction-aware fields derived from baseContext; prefer \`setup\`, \`scores\`, \`conflicts\`, \`risk\`, \`decisionHints\`, \`mtf\`, \`volatility\`, \`participation\`, and \`relative\` for quick gate checks before inspecting raw nested context.
|
|
915
|
-
Always inspect \`payload.additionalIndicators.marketContext\` when present:
|
|
916
|
-
\u2022 \`marketContext.execution.binanceCoinbaseSpread\`: AI-friendly BTC spread view projected from \`payload.additionalIndicators.baseContext.relative.execution.venueSpread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
|
|
917
|
-
\u2022 \`marketContext.participation.trueDelta\`: Binance taker buy/sell volume delta from kline payload when \`source=kline_taker_volume\`; otherwise absent/unavailable.
|
|
918
|
-
\u2022 \`marketContext.participation.tradeFlow\`: Binance aggTrades buy/sell pressure buckets when available.
|
|
919
|
-
\u2022 \`marketContext.relative.marketBreadths.top5|top10|top30|top50|top100\`: equal/volume-weighted alt-basket return, advance/decline ratio, and MA breadth for the five versioned Binance breadth universes. \`marketBreadth\` remains the top30 primary view used by existing gates.
|
|
920
|
-
\u2022 \`marketContext.relative.targetVsBtc\`: target/BTC ratio returns, alpha, beta, and short-window correlation; use it to decide whether the target is leading or lagging BTC in the signal direction.
|
|
921
|
-
\u2022 \`marketContext.relative.btcAltRegime\`: Binance-derived BTC-vs-alt basket regime, BTC/alt 24h returns, BTC turnover share, and alt dispersion; use it as a broad alt-market risk pocket.
|
|
922
|
-
\u2022 \`marketContext.relative.cmcGlobal\`: historical CoinMarketCap global market metrics: total/alt market cap, total/alt volume, BTC/ETH dominance and 24h changes, active markets, \`interval\`, and \`altLiquidityRegime\`.
|
|
923
|
-
\u2022 \`marketContext.relative.cmcReferenceAssets\`: historical CoinMarketCap BTC/ETH market-cap and volume context, ETH/BTC market-cap ratio, ETH-vs-BTC volume ratio, \`interval\`, and \`referenceLiquidityRegime\`.
|
|
924
|
-
\u2022 \`marketContext.relative.cmcExchangeLiquidity\`: historical CoinMarketCap major-exchange liquidity aggregate: total volume, 24h volume change, Binance share, concentration, and \`liquidityRegime\`.
|
|
925
|
-
\u2022 \`marketContext.relative.cmcFearGreed\`: historical daily CoinMarketCap Fear & Greed sentiment index: value, classification, 24h/7d value changes, and \`sentimentRegime\`.
|
|
926
|
-
\u2022 \`marketContext.relative.cmcIndexes\`: historical daily CoinMarketCap CMC100/CMC20 index values, 24h changes, top constituents, CMC20/CMC100 ratio, and \`indexRegime\`.
|
|
927
|
-
\u2022 \`marketContext.relative.referenceTradeFlow\`: BTC/ETH reference trade-flow summary used for broad market pressure when the target symbol itself is not BTC/ETH.
|
|
928
|
-
If those fields exist, use them as a more explicit hint instead of trying to re-derive the same idea from raw lines or points.
|
|
929
|
-
If \`baseContext.derivatives\` exists, its top-level \`summary\` and \`intervals\` are the primary BTCUSDT Coinalyze benchmark context for the time of the signal. \`secondaryReferenceSymbol\` identifies the ETHUSDT secondary benchmark, and \`referenceContexts\` contains BTCUSDT/ETHUSDT plus configured extra reference symbols such as BNBUSDT/SOLUSDT/TRXUSDT/XRPUSDT. If \`targetContext\` or \`targetDerived\` exists, those fields are the Coinalyze context for the actual target coin; use them as target-specific positioning evidence, but do not infer target-coin derivatives when they are absent.
|
|
930
|
-
Key patterns:
|
|
931
|
-
\u2022 current shared state: prefer \`payload.additionalIndicators.baseContext\`
|
|
932
|
-
\u2022 recent historical series: \`payload.indicators\`
|
|
933
|
-
\u2022 strategy service keys are possible as well, for example \`touches\`, \`distance\`, timing flags, and other setup-specific summaries
|
|
934
|
-
|
|
935
|
-
How to analyze, in order:
|
|
936
|
-
1. Start with price structure and the setup geometry or context in \`payload.figures\`. This has higher priority than indicators.
|
|
937
|
-
2. Then use \`payload.additionalIndicators.baseContext\` and other explicit strategy-specific context fields.
|
|
938
|
-
3. Then assess confirmation or conflict from the current shared state and recent coin indicator history.
|
|
939
|
-
4. Then evaluate BTC context.
|
|
940
|
-
5. Only after that choose \`direction\`, \`quality\`, and whether an extra confirmation level is required.
|
|
941
|
-
6. If strong conflicts exist, reduce quality or set direction to \`null\`.
|
|
942
|
-
|
|
943
|
-
Explicit conflict rules:
|
|
944
|
-
- If the figure or price structure is invalid or doubtful, indicators must not rescue the setup.
|
|
945
|
-
- If strategy-specific helper fields explicitly say the signal is not confirmed yet, lacks margin, or requires waiting, do not overstate quality.
|
|
946
|
-
- If the structure is acceptable but BTC or key indicators noticeably conflict, quality is usually \`<= 3\`.
|
|
947
|
-
- If \`baseContext.derivatives.referenceContexts\` exists, check \`primaryReferenceSymbol\` first as the BTC benchmark, then compare \`secondaryReferenceSymbol\`/ETHUSDT and any target-specific \`targetDerived\`. If \`targetDerived\` exists, compare it to the primary reference instead of treating reference pressure as the target coin's own pressure.
|
|
948
|
-
- If top-level \`baseContext.derivatives.summary.riskFlags\` contains \`crowded_long\` for a LONG or \`crowded_short\` for a SHORT, treat that as broad-market crowded positioning. If \`targetDerived.riskFlags\` contains the same directional crowding, treat that as target-specific crowded positioning.
|
|
949
|
-
- If top-level \`baseContext.derivatives.summary.directionAligned=false\`, explicitly mention the broad-market derivatives conflict in \`confirmations\` or \`qualityReason\`. If \`targetDerived.directionAligned=false\`, explicitly mention the target-specific derivatives conflict.
|
|
950
|
-
- If \`baseContext.derivatives\` is absent, stale, or \`missing_derivatives\`, do not infer Coinalyze conclusions and do not penalize the signal just because that data is missing.
|
|
951
|
-
- Use \`baseContext.regime.session\` directly as the canonical session/liquidity regime: asia is often thinner, europe/us are more active, and overlaps can amplify both momentum and noise. Do not reject a signal solely because of session, but mention clear session support or conflict in \`confirmations\` or \`qualityReason\`.
|
|
952
|
-
- If \`marketContext.execution.binanceCoinbaseSpread.available=true\` and \`severity=elevated/wide\`, treat it as cross-exchange divergence or BTC liquidity risk. Do not use the spread as a standalone long/short signal, but reduce confidence or require more confirmation when the rest of the structure is weak or BTC context conflicts.
|
|
953
|
-
- If \`marketContext.execution.binanceCoinbaseSpread\` is missing or \`available=false\`, do not infer anything from Binance/Coinbase spread and do not penalize the signal just because it is absent.
|
|
954
|
-
- If \`marketContext.participation.trueDelta.available=true\`, use it as better participation evidence than OHLCV-derived proxy delta; still do not let delta override invalid price structure.
|
|
955
|
-
- If \`marketContext.participation.tradeFlow.available=true\` and \`stale=false\`, use it as direct lower-timeframe participation evidence. Treat stale or missing tradeFlow as absent, not as negative evidence.
|
|
956
|
-
- If \`marketContext.relative.marketBreadth.available=true\` and \`stale=false\`, use it as broad alt-market support/conflict. Breadth is contextual; do not let it override the target symbol structure.
|
|
957
|
-
- If \`marketContext.relative.targetVsBtc.available=true\`, treat positive target/BTC ratio trend as support for alt LONGs and negative ratio trend as support for alt SHORTs; ignore it when the target structure is stronger and clearly explains the setup.
|
|
958
|
-
- If \`marketContext.relative.btcAltRegime.available=true\` and \`stale=false\`, treat \`alt_lead\`/\`risk_on\` as broad support for alt LONGs and \`btc_lead\`/\`risk_off\` as pressure against alt LONGs or support for cautious alt SHORTs. Do not use it as a standalone entry reason.
|
|
959
|
-
- If \`marketContext.relative.cmcGlobal.available=true\` and \`stale=false\`, use falling alt market cap/volume or rising BTC dominance as broad risk pressure for alt LONGs. Treat missing CMC history as absent context, not a bearish signal.
|
|
960
|
-
- If \`marketContext.relative.cmcReferenceAssets.available=true\` and \`stale=false\`, use \`eth_led\` as broad support for ETH/high-beta alt strength and \`btc_led\`/\`thin\` as broad caution. Do not describe BTC/ETH reference history as target-symbol flow.
|
|
961
|
-
- If \`marketContext.relative.cmcExchangeLiquidity.available=true\` and \`stale=false\`, treat \`contracting\`, \`thin\`, or \`concentrated\` as broad liquidity risk; \`expanding\` or \`balanced\` supports cleaner execution context but is not a standalone entry reason.
|
|
962
|
-
- If \`marketContext.relative.cmcFearGreed.available=true\` and \`stale=false\`, use \`risk_on\` as broad support for LONGs and \`risk_off\`/\`capitulation\` as broad pressure. Treat \`euphoric\` as overheating/chase caution, not as standalone SHORT proof.
|
|
963
|
-
- If \`marketContext.relative.cmcIndexes.available=true\` and \`stale=false\`, use \`top20_led\` as broad support for mega-cap leadership, \`large_cap_led\` as broader CMC100 participation, and \`risk_off\` as broad pressure. Do not use CMC index history as a standalone entry reason.
|
|
964
|
-
- If \`marketContext.relative.referenceTradeFlow.available=true\`, treat BTC/ETH trade-flow as broad market context only. For alt symbols, do not describe it as the target coin's own flow.
|
|
965
|
-
- If the current signal is not confirmed (\`direction=null\`), name the main reason briefly in \`comment\`.
|
|
966
|
-
If you use the structured fields, include the main reason in \`qualityReason\` or \`triggerInvalidation\`.
|
|
967
|
-
|
|
968
|
-
Rules for \`direction\` / TP / SL:
|
|
969
|
-
- \`direction = LONG\` only if the data confirms the existing LONG signal; \`SHORT\` only if the data confirms the existing SHORT signal; otherwise \`null\`.
|
|
970
|
-
- For LONG, the expected relation is usually \`stopLossPrice < currentPrice < takeProfitPrice\`.
|
|
971
|
-
- For SHORT, the expected relation is usually \`takeProfitPrice < currentPrice < stopLossPrice\`.
|
|
972
|
-
- Do not optimize or recalculate TP/SL for a "better trade"; only assess whether the already supplied levels are coherent.
|
|
973
|
-
- If \`direction = null\`, then \`takeProfitPrice = null\` and \`stopLossPrice = null\`.
|
|
974
|
-
- If \`needRetest = false\`, then \`retestPrice = null\`.
|
|
975
|
-
- If \`needRetest = true\`, \`retestPrice\` must be a finite number tied to a meaningful retest or breakout level.
|
|
976
|
-
- Before responding, sanity-check the consistency of \`direction\`, TP/SL, and the current price.
|
|
977
|
-
|
|
978
|
-
Quality scale:
|
|
979
|
-
- 1: poor or chaotic setup, strong conflicts, signal not structurally confirmed
|
|
980
|
-
- 2: weak setup, few confirmations, more of a watch or reject
|
|
981
|
-
- 3: average setup, some structure exists, but notable conflicts remain
|
|
982
|
-
- 4: good setup, several confirmations, structure is mostly coherent
|
|
983
|
-
- 5: very strong setup, clean structure, confirmations, and internally coherent levels
|
|
984
|
-
|
|
985
|
-
Requirements for useful structured analysis:
|
|
986
|
-
- Include 2-4 concrete factors for or against confirmation in \`confirmations\`.
|
|
987
|
-
- Explicitly mention the role of the key figure or structural state, for example breakout, retest, false break, touch, or lack of confirmation.
|
|
988
|
-
- Explicitly mention BTC context as supportive, neutral, or conflicting.
|
|
989
|
-
- Explain why the quality score is what it is.
|
|
990
|
-
- If the signal is not confirmed (\`direction=null\`), state clearly what must change for confirmation.
|
|
991
|
-
- In \`retestPlan\`, avoid technical placeholders like \`needRetest=false @ null\`; write a human explanation.
|
|
992
|
-
- Do not simply restate JSON fields; add interpretation and decision logic.
|
|
993
|
-
|
|
994
|
-
Rules for using trimmed series (last 5 values):
|
|
995
|
-
- Do not make strong long-term conclusions from only 5 points.
|
|
996
|
-
- Use 4h and 1d series as brief context, not full history.
|
|
997
|
-
- If the data is too limited for confidence, reduce quality and use cautious wording.
|
|
998
|
-
|
|
999
|
-
Short few-shot examples:
|
|
1000
|
-
{"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."}
|
|
1001
|
-
{"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."}
|
|
1002
|
-
|
|
1003
|
-
Return only the JSON object, with no extra characters.
|
|
1004
|
-
${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
|
|
1005
|
-
`;
|
|
1006
|
-
var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
|
|
1007
|
-
var getDeterministicAiGateContext = (payload) => {
|
|
1008
|
-
const additionalIndicators = asRecord(payload.additionalIndicators);
|
|
1009
|
-
const candidates = [
|
|
1010
|
-
additionalIndicators,
|
|
1011
|
-
...Object.values(additionalIndicators ?? {}).map(asRecord)
|
|
1012
|
-
].filter((value) => Boolean(value));
|
|
1013
|
-
return candidates.find(
|
|
1014
|
-
(candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
|
|
1015
|
-
) ?? null;
|
|
738
|
+
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
739
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
740
|
+
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
741
|
+
if (cachedMatcher) {
|
|
742
|
+
const resolved2 = cachedMatcher(moduleName);
|
|
743
|
+
return resolved2 || null;
|
|
744
|
+
}
|
|
745
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
746
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
747
|
+
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
748
|
+
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
752
|
+
if (loadedConfig.resultType !== "success") {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
const matchPath = createMatchPath(
|
|
756
|
+
loadedConfig.absoluteBaseUrl,
|
|
757
|
+
loadedConfig.paths
|
|
758
|
+
);
|
|
759
|
+
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
760
|
+
".ts",
|
|
761
|
+
".tsx",
|
|
762
|
+
".mts",
|
|
763
|
+
".cts",
|
|
764
|
+
".js",
|
|
765
|
+
".jsx",
|
|
766
|
+
".mjs",
|
|
767
|
+
".cjs",
|
|
768
|
+
".json"
|
|
769
|
+
]) || "";
|
|
770
|
+
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
771
|
+
const resolved = matcher(moduleName);
|
|
772
|
+
return resolved || null;
|
|
1016
773
|
};
|
|
1017
|
-
var
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
774
|
+
var toImportSpecifier = (moduleName) => {
|
|
775
|
+
if (moduleName.startsWith("file://")) {
|
|
776
|
+
return moduleName;
|
|
777
|
+
}
|
|
778
|
+
if (import_path.default.isAbsolute(moduleName)) {
|
|
779
|
+
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
780
|
+
}
|
|
781
|
+
return moduleName;
|
|
782
|
+
};
|
|
783
|
+
var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
|
|
784
|
+
var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
|
|
785
|
+
var isBareModuleSpecifier = (moduleName) => {
|
|
786
|
+
const normalized = String(moduleName ?? "").trim();
|
|
787
|
+
if (!normalized) {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
return true;
|
|
794
|
+
};
|
|
795
|
+
var importConfigFile = async (configFilePath) => {
|
|
796
|
+
const ext = import_path.default.extname(configFilePath).toLowerCase();
|
|
797
|
+
const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
|
|
798
|
+
if (ext === ".ts" || ext === ".mts") {
|
|
799
|
+
const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
|
|
800
|
+
await ensureTsNodeRegistered();
|
|
801
|
+
await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
|
|
802
|
+
return requireFn(configFilePath);
|
|
803
|
+
}
|
|
804
|
+
return import(
|
|
805
|
+
/* webpackIgnore: true */
|
|
806
|
+
configFileUrl
|
|
1029
807
|
);
|
|
1030
|
-
|
|
1031
|
-
|
|
808
|
+
};
|
|
809
|
+
var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
810
|
+
const normalized = String(moduleName ?? "").trim();
|
|
811
|
+
if (!normalized) {
|
|
812
|
+
return {};
|
|
813
|
+
}
|
|
814
|
+
let modulePath = normalized;
|
|
815
|
+
if (normalized.startsWith("file://")) {
|
|
816
|
+
try {
|
|
817
|
+
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
818
|
+
} catch {
|
|
819
|
+
modulePath = normalized;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const requireFn = getRequireFn(
|
|
823
|
+
import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
|
|
1032
824
|
);
|
|
1033
|
-
|
|
1034
|
-
|
|
825
|
+
if (isTsModulePath(modulePath)) {
|
|
826
|
+
await ensureTsNodeRegistered();
|
|
827
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
828
|
+
return requireFn(modulePath);
|
|
829
|
+
}
|
|
830
|
+
if (isBareModuleSpecifier(normalized)) {
|
|
831
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
832
|
+
try {
|
|
833
|
+
return requireFn(normalized);
|
|
834
|
+
} catch (error) {
|
|
835
|
+
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
836
|
+
normalized,
|
|
837
|
+
cwd
|
|
838
|
+
);
|
|
839
|
+
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
840
|
+
return requireFn(resolvedByTsconfig);
|
|
841
|
+
}
|
|
842
|
+
throw error;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
return await import(
|
|
847
|
+
/* webpackIgnore: true */
|
|
848
|
+
toImportSpecifier(normalized)
|
|
849
|
+
);
|
|
850
|
+
} catch (error) {
|
|
851
|
+
if (isTsModulePath(modulePath)) {
|
|
852
|
+
await ensureTsNodeRegistered();
|
|
853
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
854
|
+
return requireFn(modulePath);
|
|
855
|
+
}
|
|
856
|
+
throw error;
|
|
857
|
+
}
|
|
1035
858
|
};
|
|
1036
|
-
var
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
var
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
859
|
+
var resolveExportedConfig = (moduleExports) => {
|
|
860
|
+
const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
|
|
861
|
+
return normalizeConfig(candidate);
|
|
862
|
+
};
|
|
863
|
+
var findConfigFilePath = (cwd) => {
|
|
864
|
+
let currentDir = import_path.default.resolve(cwd);
|
|
865
|
+
while (true) {
|
|
866
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
867
|
+
const fullPath = import_path.default.join(currentDir, fileName);
|
|
868
|
+
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
869
|
+
return fullPath;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const parentDir = import_path.default.dirname(currentDir);
|
|
873
|
+
if (parentDir === currentDir) {
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
currentDir = parentDir;
|
|
1045
877
|
}
|
|
1046
|
-
const settingsModelName = typeof settings.AI_MODEL === "string" ? settings.AI_MODEL.trim() : "";
|
|
1047
|
-
return settingsModelName || DEFAULT_AI_MODEL;
|
|
1048
878
|
};
|
|
1049
|
-
var
|
|
1050
|
-
const
|
|
1051
|
-
if (!
|
|
879
|
+
var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
880
|
+
const normalized = String(moduleName ?? "").trim();
|
|
881
|
+
if (!normalized) {
|
|
882
|
+
return "";
|
|
883
|
+
}
|
|
884
|
+
if (normalized.startsWith("file://")) {
|
|
885
|
+
try {
|
|
886
|
+
return (0, import_url.fileURLToPath)(normalized);
|
|
887
|
+
} catch {
|
|
888
|
+
return normalized;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (import_path.default.isAbsolute(normalized)) {
|
|
892
|
+
return normalized;
|
|
893
|
+
}
|
|
894
|
+
if (isRelativeModulePath(normalized)) {
|
|
895
|
+
return import_path.default.resolve(cwd, normalized);
|
|
896
|
+
}
|
|
897
|
+
return normalized;
|
|
898
|
+
};
|
|
899
|
+
var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
900
|
+
const cached = cachedByCwd.get(cwd);
|
|
901
|
+
if (cached) {
|
|
902
|
+
return cached;
|
|
903
|
+
}
|
|
904
|
+
const configFilePath = findConfigFilePath(cwd);
|
|
905
|
+
if (!configFilePath) {
|
|
906
|
+
cachedByCwd.set(cwd, {});
|
|
1052
907
|
return {};
|
|
1053
908
|
}
|
|
1054
|
-
let hostname = "";
|
|
1055
909
|
try {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
910
|
+
const moduleExports = await importConfigFile(configFilePath);
|
|
911
|
+
const config = resolveExportedConfig(moduleExports);
|
|
912
|
+
cachedByCwd.set(cwd, config);
|
|
913
|
+
if (!announcedConfigFile.has(configFilePath)) {
|
|
914
|
+
announcedConfigFile.add(configFilePath);
|
|
915
|
+
import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
916
|
+
}
|
|
917
|
+
return config;
|
|
918
|
+
} catch (error) {
|
|
919
|
+
import_logger.logger.log(
|
|
920
|
+
"warn",
|
|
921
|
+
"Failed to load TradeJS config from %s: %s",
|
|
922
|
+
configFilePath,
|
|
923
|
+
String(error)
|
|
924
|
+
);
|
|
925
|
+
cachedByCwd.set(cwd, {});
|
|
1061
926
|
return {};
|
|
1062
927
|
}
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
// src/strategy/manifests.ts
|
|
931
|
+
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
932
|
+
var sharedRegistryScope = globalThis;
|
|
933
|
+
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
934
|
+
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
935
|
+
});
|
|
936
|
+
var createStrategyRegistryState = () => ({
|
|
937
|
+
strategyCreators: /* @__PURE__ */ new Map(),
|
|
938
|
+
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
939
|
+
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
940
|
+
pluginsLoadPromise: null
|
|
941
|
+
});
|
|
942
|
+
var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
943
|
+
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
944
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
945
|
+
let state = registryStateByProjectRoot.get(projectRoot);
|
|
946
|
+
if (!state) {
|
|
947
|
+
state = createStrategyRegistryState();
|
|
948
|
+
registryStateByProjectRoot.set(projectRoot, state);
|
|
949
|
+
}
|
|
1063
950
|
return {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
}
|
|
951
|
+
projectRoot,
|
|
952
|
+
state
|
|
1067
953
|
};
|
|
1068
954
|
};
|
|
1069
|
-
var
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
955
|
+
var toUniqueModules = (modules = []) => [
|
|
956
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
957
|
+
];
|
|
958
|
+
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
959
|
+
const config = await loadTradejsConfig(cwd);
|
|
960
|
+
return {
|
|
961
|
+
strategyModules: toUniqueModules(config.strategies),
|
|
962
|
+
indicatorModules: toUniqueModules(config.indicators)
|
|
963
|
+
};
|
|
964
|
+
};
|
|
965
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
966
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
const candidate = moduleExport;
|
|
970
|
+
if (Array.isArray(candidate[key])) {
|
|
971
|
+
return candidate[key];
|
|
972
|
+
}
|
|
973
|
+
const defaultExport = candidate.default;
|
|
974
|
+
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
975
|
+
return defaultExport[key];
|
|
976
|
+
}
|
|
977
|
+
return null;
|
|
978
|
+
};
|
|
979
|
+
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
980
|
+
const strategyEntries = extractModuleEntries(
|
|
981
|
+
moduleExport,
|
|
982
|
+
"strategyEntries"
|
|
983
|
+
);
|
|
984
|
+
return strategyEntries ? { strategyEntries } : null;
|
|
985
|
+
};
|
|
986
|
+
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
987
|
+
const indicatorEntries = extractModuleEntries(
|
|
988
|
+
moduleExport,
|
|
989
|
+
"indicatorEntries"
|
|
990
|
+
);
|
|
991
|
+
return indicatorEntries ? { indicatorEntries } : null;
|
|
992
|
+
};
|
|
993
|
+
var registerEntries = (entries, source, state) => {
|
|
994
|
+
for (const entry of entries) {
|
|
995
|
+
const strategyName = entry.manifest?.name;
|
|
996
|
+
if (!strategyName) {
|
|
997
|
+
import_logger2.logger.warn("Skip strategy entry without name from %s", source);
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (state.strategyCreators.has(strategyName)) {
|
|
1001
|
+
import_logger2.logger.warn(
|
|
1002
|
+
'Skip duplicate strategy "%s" from %s: already registered',
|
|
1003
|
+
strategyName,
|
|
1004
|
+
source
|
|
1005
|
+
);
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
1009
|
+
state.strategyEntriesMap.set(strategyName, entry);
|
|
1010
|
+
materializeStrategyCreator(strategyName, state);
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
var materializeStrategyCreator = (strategyName, state) => {
|
|
1014
|
+
if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const entry = state.strategyEntriesMap.get(strategyName);
|
|
1018
|
+
if (!entry) return;
|
|
1019
|
+
state.strategyCreators.set(
|
|
1020
|
+
strategyName,
|
|
1021
|
+
sharedStrategyRegistry.strategyRuntimeFactory({
|
|
1022
|
+
strategyName,
|
|
1023
|
+
defaults: entry.defaults,
|
|
1024
|
+
createCore: entry.createCore,
|
|
1025
|
+
manifest: entry.manifest,
|
|
1026
|
+
detectorKey: entry.detectorKey,
|
|
1027
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1028
|
+
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
1029
|
+
})
|
|
1030
|
+
);
|
|
1031
|
+
};
|
|
1032
|
+
var setStrategyRuntimeFactory = (factory) => {
|
|
1033
|
+
sharedStrategyRegistry.strategyRuntimeFactory = factory;
|
|
1034
|
+
for (const state of registryStateByProjectRoot.values()) {
|
|
1035
|
+
for (const strategyName of state.strategyEntriesMap.keys()) {
|
|
1036
|
+
materializeStrategyCreator(strategyName, state);
|
|
1037
|
+
}
|
|
1077
1038
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1039
|
+
};
|
|
1040
|
+
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1041
|
+
if (typeof importTradejsModule === "function") {
|
|
1042
|
+
return importTradejsModule(moduleName, cwd);
|
|
1081
1043
|
}
|
|
1082
|
-
return
|
|
1044
|
+
return import(
|
|
1045
|
+
/* webpackIgnore: true */
|
|
1046
|
+
moduleName
|
|
1047
|
+
);
|
|
1083
1048
|
};
|
|
1084
|
-
var
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1049
|
+
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
1050
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1051
|
+
if (!state.pluginsLoadPromise) {
|
|
1052
|
+
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
1053
|
+
state.pluginsLoadPromise = (async () => {
|
|
1054
|
+
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
1055
|
+
const strategySet = new Set(strategyModules);
|
|
1056
|
+
const indicatorSet = new Set(indicatorModules);
|
|
1057
|
+
const pluginModuleNames = [
|
|
1058
|
+
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
1059
|
+
];
|
|
1060
|
+
if (!pluginModuleNames.length) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
for (const moduleName of pluginModuleNames) {
|
|
1064
|
+
try {
|
|
1065
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
1066
|
+
moduleName,
|
|
1067
|
+
projectRoot
|
|
1068
|
+
);
|
|
1069
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1070
|
+
resolvedModuleName,
|
|
1071
|
+
projectRoot
|
|
1072
|
+
);
|
|
1073
|
+
if (strategySet.has(moduleName)) {
|
|
1074
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1075
|
+
if (!pluginDefinition) {
|
|
1076
|
+
import_logger2.logger.warn(
|
|
1077
|
+
'Skip strategy plugin "%s": export { strategyEntries } is missing',
|
|
1078
|
+
moduleName
|
|
1079
|
+
);
|
|
1080
|
+
} else {
|
|
1081
|
+
registerEntries(
|
|
1082
|
+
pluginDefinition.strategyEntries,
|
|
1083
|
+
moduleName,
|
|
1084
|
+
state
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (indicatorSet.has(moduleName)) {
|
|
1089
|
+
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
1090
|
+
if (!indicatorPluginDefinition) {
|
|
1091
|
+
import_logger2.logger.warn(
|
|
1092
|
+
'Skip indicator plugin "%s": export { indicatorEntries } is missing',
|
|
1093
|
+
moduleName
|
|
1094
|
+
);
|
|
1095
|
+
} else {
|
|
1096
|
+
(0, import_indicators.registerIndicatorEntries)(
|
|
1097
|
+
indicatorPluginDefinition.indicatorEntries,
|
|
1098
|
+
moduleName,
|
|
1099
|
+
projectRoot
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
1104
|
+
import_logger2.logger.warn(
|
|
1105
|
+
'Skip plugin "%s": no strategy/indicator sections requested in config',
|
|
1106
|
+
moduleName
|
|
1107
|
+
);
|
|
1103
1108
|
}
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
import_logger2.logger.warn(
|
|
1111
|
+
'Failed to load plugin "%s": %s',
|
|
1112
|
+
moduleName,
|
|
1113
|
+
String(error)
|
|
1114
|
+
);
|
|
1104
1115
|
}
|
|
1105
|
-
}
|
|
1116
|
+
}
|
|
1106
1117
|
})();
|
|
1107
|
-
modelPromise.catch(() => {
|
|
1108
|
-
aiModelCache.delete(cacheKey);
|
|
1109
|
-
});
|
|
1110
|
-
aiModelCache.set(cacheKey, modelPromise);
|
|
1111
|
-
}
|
|
1112
|
-
return modelPromise;
|
|
1113
|
-
};
|
|
1114
|
-
var getAiModel = async (userName = "root", requestedModelName) => {
|
|
1115
|
-
const settings = await getAiSettings(userName);
|
|
1116
|
-
const resolvedModelName = resolveAiModelName(settings, requestedModelName);
|
|
1117
|
-
try {
|
|
1118
|
-
return await createAiModel(userName, resolvedModelName);
|
|
1119
|
-
} catch (error) {
|
|
1120
|
-
aiModelCache.delete(getAiModelCacheKey(userName, resolvedModelName));
|
|
1121
|
-
userSettingsCache.delete(userName);
|
|
1122
|
-
throw error;
|
|
1123
1118
|
}
|
|
1119
|
+
await state.pluginsLoadPromise;
|
|
1124
1120
|
};
|
|
1125
|
-
var
|
|
1126
|
-
await ensureStrategyPluginsLoaded();
|
|
1121
|
+
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd()) => {
|
|
1122
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
1123
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
1124
|
+
return state.strategyCreators.get(name);
|
|
1127
1125
|
};
|
|
1128
|
-
var
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1126
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
1127
|
+
if (!name) {
|
|
1128
|
+
return void 0;
|
|
1131
1129
|
}
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
{
|
|
1151
|
-
type: "text",
|
|
1152
|
-
text: humanPrompt
|
|
1153
|
-
}
|
|
1154
|
-
]
|
|
1130
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
1131
|
+
return state.strategyManifestsMap.get(name);
|
|
1132
|
+
};
|
|
1133
|
+
var strategies = new Proxy(
|
|
1134
|
+
{},
|
|
1135
|
+
{
|
|
1136
|
+
get: (_target, property) => {
|
|
1137
|
+
if (typeof property !== "string") {
|
|
1138
|
+
return void 0;
|
|
1139
|
+
}
|
|
1140
|
+
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
1141
|
+
},
|
|
1142
|
+
ownKeys: () => {
|
|
1143
|
+
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
1144
|
+
},
|
|
1145
|
+
getOwnPropertyDescriptor: () => ({
|
|
1146
|
+
enumerable: true,
|
|
1147
|
+
configurable: true
|
|
1155
1148
|
})
|
|
1156
|
-
);
|
|
1157
|
-
let response;
|
|
1158
|
-
try {
|
|
1159
|
-
response = await model.invoke(messages);
|
|
1160
|
-
} catch (error) {
|
|
1161
|
-
throw getAiInvocationError(error);
|
|
1162
1149
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
// src/strategy/policyProfiles.ts
|
|
1153
|
+
var profileMatches = (profile, universe, assetClass) => {
|
|
1154
|
+
const { appliesTo } = profile;
|
|
1155
|
+
if (!appliesTo) return true;
|
|
1156
|
+
if (appliesTo.universes?.length && (!universe || !appliesTo.universes.includes(universe))) {
|
|
1157
|
+
return false;
|
|
1166
1158
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
if (!options.signal) {
|
|
1170
|
-
return normalized;
|
|
1159
|
+
if (appliesTo.assetClasses?.length && (!assetClass || !appliesTo.assetClasses.includes(assetClass))) {
|
|
1160
|
+
return false;
|
|
1171
1161
|
}
|
|
1172
|
-
return
|
|
1173
|
-
options.signal,
|
|
1174
|
-
normalized,
|
|
1175
|
-
options.payload
|
|
1176
|
-
);
|
|
1177
|
-
};
|
|
1178
|
-
var runAiPromptLocal = async (signal, options = {}) => {
|
|
1179
|
-
await ensureAiStrategyPluginsLoaded();
|
|
1180
|
-
const payload = options.payload ?? buildAiPayload(signal);
|
|
1181
|
-
const gateContext = getDeterministicAiGateContext(payload);
|
|
1182
|
-
const signalDirection = getSignalDirection(signal);
|
|
1183
|
-
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
1184
|
-
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
1185
|
-
return postProcessLocalAiAnalysisByStrategy(
|
|
1186
|
-
signal,
|
|
1187
|
-
{
|
|
1188
|
-
direction: approvalAllowedNow ? signalDirection : null,
|
|
1189
|
-
quality: deterministicQuality,
|
|
1190
|
-
needRetest: !approvalAllowedNow,
|
|
1191
|
-
retestPrice: null,
|
|
1192
|
-
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
1193
|
-
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
1194
|
-
},
|
|
1195
|
-
payload
|
|
1196
|
-
);
|
|
1162
|
+
return true;
|
|
1197
1163
|
};
|
|
1198
|
-
var
|
|
1199
|
-
const
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
{
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1164
|
+
var resolveStrategyPolicyProfile = (manifest, params) => {
|
|
1165
|
+
const profiles = manifest?.policyProfiles ?? [];
|
|
1166
|
+
if (!profiles.length) {
|
|
1167
|
+
const inferredId = params.profileId ?? (params.universe === "tradfi" ? "tradfi" : void 0);
|
|
1168
|
+
if (!inferredId) return void 0;
|
|
1169
|
+
if (inferredId !== "crypto" && inferredId !== "tradfi") {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
`Unknown policy profile "${inferredId}" for strategy "${manifest?.name}"`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (params.universe && inferredId !== params.universe) {
|
|
1175
|
+
throw new Error(
|
|
1176
|
+
`Policy profile "${inferredId}" is not compatible with ${params.universe}`
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
return {
|
|
1180
|
+
id: inferredId,
|
|
1181
|
+
appliesTo: { universes: [inferredId] },
|
|
1182
|
+
marketDataRequirements: inferredId === "crypto" ? ["crypto.btcReference"] : [],
|
|
1183
|
+
...manifest?.mlAdapter ? {
|
|
1184
|
+
entryRuntimeDefaults: {
|
|
1185
|
+
ml: {
|
|
1186
|
+
modelKey: inferredId === "crypto" ? manifest.name : `${manifest.name}:tradfi`
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
} : {}
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
if (params.profileId) {
|
|
1193
|
+
const profile = profiles.find(({ id }) => id === params.profileId);
|
|
1194
|
+
if (!profile) {
|
|
1195
|
+
throw new Error(
|
|
1196
|
+
`Unknown policy profile "${params.profileId}" for strategy "${manifest?.name}"`
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
if (!profileMatches(profile, params.universe, params.assetClass)) {
|
|
1200
|
+
throw new Error(
|
|
1201
|
+
`Policy profile "${params.profileId}" is not compatible with ${params.universe ?? "unknown"}:${params.assetClass ?? "unknown"}`
|
|
1202
|
+
);
|
|
1211
1203
|
}
|
|
1204
|
+
return profile;
|
|
1205
|
+
}
|
|
1206
|
+
const matching = profiles.filter(
|
|
1207
|
+
(profile) => profileMatches(profile, params.universe, params.assetClass)
|
|
1212
1208
|
);
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
};
|
|
1216
|
-
|
|
1217
|
-
// src/strategyAdapters/ml.ts
|
|
1218
|
-
var defaultMlAdapter = {
|
|
1219
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
1220
|
-
};
|
|
1221
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
1222
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
1223
|
-
getStrategyManifest(strategy),
|
|
1224
|
-
profileId
|
|
1209
|
+
const defaultProfile = matching.find(
|
|
1210
|
+
({ id }) => id === manifest?.defaultPolicyProfileId
|
|
1225
1211
|
);
|
|
1226
|
-
|
|
1227
|
-
return {
|
|
1228
|
-
...defaultMlAdapter,
|
|
1229
|
-
...strategyAdapter
|
|
1230
|
-
};
|
|
1212
|
+
return defaultProfile ?? matching[0];
|
|
1231
1213
|
};
|
|
1214
|
+
var getStrategyProfileAiAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.aiAdapter ?? manifest?.aiAdapter;
|
|
1215
|
+
var getStrategyProfileMlAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.mlAdapter ?? manifest?.mlAdapter;
|
|
1232
1216
|
|
|
1233
|
-
// src/
|
|
1234
|
-
var
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1217
|
+
// src/strategyAdapters/ai.ts
|
|
1218
|
+
var toRecord2 = (value) => {
|
|
1219
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1220
|
+
return {};
|
|
1221
|
+
}
|
|
1222
|
+
return value;
|
|
1239
1223
|
};
|
|
1240
|
-
var
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
1245
|
-
const nextSignal = {
|
|
1246
|
-
...normalizedSignal,
|
|
1247
|
-
indicators: {
|
|
1248
|
-
...normalizedSignal?.indicators ?? {}
|
|
1249
|
-
}
|
|
1224
|
+
var buildBaseAiPayload = (signal) => {
|
|
1225
|
+
const additionalIndicators = {
|
|
1226
|
+
...toRecord2(signal.additionalIndicators),
|
|
1227
|
+
marketContext: buildAiMarketContext(signal)
|
|
1250
1228
|
};
|
|
1251
|
-
const nextContext = payload.context ? {
|
|
1252
|
-
...payload.context,
|
|
1253
|
-
strategyConfig: normalizeStrategyConfig(
|
|
1254
|
-
payload.context.strategyConfig,
|
|
1255
|
-
strategyName,
|
|
1256
|
-
profileId
|
|
1257
|
-
)
|
|
1258
|
-
} : void 0;
|
|
1259
1229
|
return {
|
|
1260
|
-
signal:
|
|
1261
|
-
|
|
1230
|
+
signal: {
|
|
1231
|
+
symbol: signal.symbol,
|
|
1232
|
+
signalId: signal.signalId,
|
|
1233
|
+
interval: signal.interval,
|
|
1234
|
+
direction: signal.direction,
|
|
1235
|
+
timestamp: signal.timestamp,
|
|
1236
|
+
strategy: signal.strategy,
|
|
1237
|
+
prices: {
|
|
1238
|
+
currentPrice: signal.prices.currentPrice,
|
|
1239
|
+
takeProfitPrice: signal.prices.takeProfitPrice,
|
|
1240
|
+
stopLossPrice: signal.prices.stopLossPrice
|
|
1241
|
+
}
|
|
1242
|
+
},
|
|
1243
|
+
figures: trimSeriesDeep(signal.figures ?? {}),
|
|
1244
|
+
indicators: buildCompactAiIndicatorsSnapshot(signal.indicators),
|
|
1245
|
+
additionalIndicators: trimSeriesDeep(additionalIndicators)
|
|
1262
1246
|
};
|
|
1263
1247
|
};
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
var
|
|
1267
|
-
var
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
var CONFIG_FILE_NAMES = [
|
|
1272
|
-
"tradejs.config.ts",
|
|
1273
|
-
"tradejs.config.mts",
|
|
1274
|
-
"tradejs.config.js",
|
|
1275
|
-
"tradejs.config.mjs",
|
|
1276
|
-
"tradejs.config.cjs"
|
|
1277
|
-
];
|
|
1278
|
-
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1279
|
-
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1280
|
-
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1281
|
-
var tsNodeRegistered = false;
|
|
1282
|
-
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1283
|
-
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1284
|
-
var getTradejsProjectCwd = (cwd) => {
|
|
1285
|
-
const explicit = String(cwd ?? "").trim();
|
|
1286
|
-
if (explicit) {
|
|
1287
|
-
return import_path.default.resolve(explicit);
|
|
1288
|
-
}
|
|
1289
|
-
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1290
|
-
if (fromEnv) {
|
|
1291
|
-
return import_path.default.resolve(fromEnv);
|
|
1292
|
-
}
|
|
1293
|
-
return process.cwd();
|
|
1248
|
+
var defaultAiAdapter = {};
|
|
1249
|
+
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
1250
|
+
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
1251
|
+
var buildAiPayloadByStrategy = (signal) => {
|
|
1252
|
+
const basePayload = buildBaseAiPayload(signal);
|
|
1253
|
+
const adapter = getSignalAiAdapter(signal);
|
|
1254
|
+
return adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
1294
1255
|
};
|
|
1295
|
-
var
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1256
|
+
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
1257
|
+
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
1258
|
+
signal,
|
|
1259
|
+
payload
|
|
1260
|
+
}) ?? "";
|
|
1261
|
+
var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => getSignalAiAdapter(signal).postProcessAnalysis?.({
|
|
1262
|
+
signal,
|
|
1263
|
+
payload,
|
|
1264
|
+
analysis
|
|
1265
|
+
}) ?? analysis;
|
|
1266
|
+
var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => {
|
|
1267
|
+
const adapter = getSignalAiAdapter(signal);
|
|
1268
|
+
const strategyAnalysis = adapter.postProcessAnalysis?.({ signal, payload, analysis }) ?? analysis;
|
|
1269
|
+
return adapter.postProcessLocalAnalysis?.({
|
|
1270
|
+
signal,
|
|
1271
|
+
payload,
|
|
1272
|
+
analysis: strategyAnalysis
|
|
1273
|
+
}) ?? strategyAnalysis;
|
|
1312
1274
|
};
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1275
|
+
|
|
1276
|
+
// src/ai.ts
|
|
1277
|
+
var parseAIResponse = (input) => {
|
|
1278
|
+
try {
|
|
1279
|
+
if (typeof input === "object" && input !== null) return input;
|
|
1280
|
+
const match = input.match(/\{[\s\S]*\}/);
|
|
1281
|
+
if (!match) throw new Error("JSON block not found");
|
|
1282
|
+
return JSON.parse(match[0]);
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
console.error("Failed to parse AI response:", err);
|
|
1285
|
+
console.log("Raw AI response:", input);
|
|
1286
|
+
return {};
|
|
1317
1287
|
}
|
|
1318
|
-
throw new TypeError("module.createRequire is not available");
|
|
1319
1288
|
};
|
|
1320
|
-
var
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
}
|
|
1325
|
-
const tsNodeModule = await import("ts-node");
|
|
1326
|
-
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1327
|
-
tsNode.register?.({
|
|
1328
|
-
transpileOnly: true,
|
|
1329
|
-
compilerOptions: {
|
|
1330
|
-
module: "Node16",
|
|
1331
|
-
moduleResolution: "node16"
|
|
1289
|
+
var normalizeResponseContent = (content) => {
|
|
1290
|
+
if (typeof content === "string" || content && typeof content === "object") {
|
|
1291
|
+
if (typeof content !== "object" || !Array.isArray(content)) {
|
|
1292
|
+
return content;
|
|
1332
1293
|
}
|
|
1333
|
-
});
|
|
1334
|
-
tsNodeRegistered = true;
|
|
1335
|
-
};
|
|
1336
|
-
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1337
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1338
|
-
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
|
-
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1342
|
-
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1343
|
-
const register = tsconfigPathsModule.register;
|
|
1344
|
-
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1345
|
-
return;
|
|
1346
1294
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
return;
|
|
1295
|
+
if (Array.isArray(content)) {
|
|
1296
|
+
const text = content.map((part) => typeof part?.text === "string" ? part.text : "").join("\n").trim();
|
|
1297
|
+
return text;
|
|
1350
1298
|
}
|
|
1351
|
-
|
|
1352
|
-
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1353
|
-
paths: loadedConfig.paths,
|
|
1354
|
-
addMatchAll: false
|
|
1355
|
-
});
|
|
1356
|
-
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
1299
|
+
return String(content ?? "");
|
|
1357
1300
|
};
|
|
1358
|
-
var
|
|
1359
|
-
const
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
1368
|
-
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
1301
|
+
var normalizeAnalysis = (raw) => {
|
|
1302
|
+
const direction = raw?.direction === "LONG" || raw?.direction === "SHORT" ? raw.direction : null;
|
|
1303
|
+
const qualityNum = typeof raw?.quality === "number" ? Math.max(1, Math.min(5, Math.round(raw.quality))) : void 0;
|
|
1304
|
+
const toNumberOrNull = (value) => {
|
|
1305
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1306
|
+
if (typeof value === "string" && value.trim()) {
|
|
1307
|
+
const parsed = Number(value);
|
|
1308
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
1309
|
+
}
|
|
1369
1310
|
return null;
|
|
1370
|
-
}
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1311
|
+
};
|
|
1312
|
+
const toText = (value) => typeof value === "string" ? value.slice(0, 400) : void 0;
|
|
1313
|
+
return {
|
|
1314
|
+
direction,
|
|
1315
|
+
quality: qualityNum,
|
|
1316
|
+
needRetest: Boolean(raw?.needRetest),
|
|
1317
|
+
retestPrice: toNumberOrNull(raw?.retestPrice),
|
|
1318
|
+
takeProfitPrice: toNumberOrNull(raw?.takeProfitPrice),
|
|
1319
|
+
stopLossPrice: toNumberOrNull(raw?.stopLossPrice),
|
|
1320
|
+
setup: toText(raw?.setup),
|
|
1321
|
+
confirmations: toText(raw?.confirmations),
|
|
1322
|
+
btcContext: toText(raw?.btcContext),
|
|
1323
|
+
retestPlan: toText(raw?.retestPlan),
|
|
1324
|
+
riskLevels: toText(raw?.riskLevels),
|
|
1325
|
+
qualityReason: toText(raw?.qualityReason),
|
|
1326
|
+
triggerInvalidation: toText(raw?.triggerInvalidation),
|
|
1327
|
+
comment: typeof raw?.comment === "string" ? raw.comment.slice(0, 1024) : ""
|
|
1328
|
+
};
|
|
1329
|
+
};
|
|
1330
|
+
var asRecord = (value) => {
|
|
1331
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1373
1332
|
return null;
|
|
1374
1333
|
}
|
|
1375
|
-
|
|
1376
|
-
loadedConfig.absoluteBaseUrl,
|
|
1377
|
-
loadedConfig.paths
|
|
1378
|
-
);
|
|
1379
|
-
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
1380
|
-
".ts",
|
|
1381
|
-
".tsx",
|
|
1382
|
-
".mts",
|
|
1383
|
-
".cts",
|
|
1384
|
-
".js",
|
|
1385
|
-
".jsx",
|
|
1386
|
-
".mjs",
|
|
1387
|
-
".cjs",
|
|
1388
|
-
".json"
|
|
1389
|
-
]) || "";
|
|
1390
|
-
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1391
|
-
const resolved = matcher(moduleName);
|
|
1392
|
-
return resolved || null;
|
|
1334
|
+
return value;
|
|
1393
1335
|
};
|
|
1394
|
-
var
|
|
1395
|
-
|
|
1396
|
-
|
|
1336
|
+
var getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
|
|
1337
|
+
var getDeterministicQuality = (gateContext) => {
|
|
1338
|
+
const deterministicQuality = Number(gateContext?.deterministicQuality);
|
|
1339
|
+
if (Number.isFinite(deterministicQuality)) {
|
|
1340
|
+
return Math.max(1, Math.min(5, Math.round(deterministicQuality)));
|
|
1397
1341
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1342
|
+
const maxAllowedQuality = Number(gateContext?.maxAllowedQuality);
|
|
1343
|
+
if (Number.isFinite(maxAllowedQuality)) {
|
|
1344
|
+
return Math.max(1, Math.min(5, Math.round(maxAllowedQuality)));
|
|
1400
1345
|
}
|
|
1401
|
-
return
|
|
1346
|
+
return Array.isArray(gateContext?.approvalBlockReasons) && gateContext.approvalBlockReasons.length > 0 || Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length > 0 ? 2 : 3;
|
|
1402
1347
|
};
|
|
1403
|
-
var
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1348
|
+
var buildAiSystemPrompt = (signal) => `
|
|
1349
|
+
You are an internal market-structure classifier for an already computed system signal.
|
|
1350
|
+
Analyze the provided JSON containing the trade, candles, indicators (for the coin and BTC across multiple timeframes), and strategy figures/context.
|
|
1351
|
+
Series data is already trimmed to the latest 5 values.
|
|
1352
|
+
|
|
1353
|
+
Important:
|
|
1354
|
+
- Do not invent missing data.
|
|
1355
|
+
- This is an internal audit/classification task, not user-facing trading advice.
|
|
1356
|
+
- Do not generate execution instructions, do not replace the original thesis with a new one, and do not provide personalized investment advice.
|
|
1357
|
+
- Use the original signal direction and levels as the anchor, but you may state that the current structure does not support them.
|
|
1358
|
+
- Respect the source strategy specified in \`signal.strategy\`.
|
|
1359
|
+
- Your goal is to explain how well the observed structure matches the existing signal and how structurally confirmed it is right now.
|
|
1360
|
+
- Do not write vague statements like "there is momentum/slope" without tying them to the decision.
|
|
1361
|
+
- Write all user-visible text fields in the requested response language. If no explicit language instruction is provided later, default to English.
|
|
1362
|
+
- If confidence is incomplete, prefer cautious wording such as "likely", "not confirmed yet", or "probably" instead of categorical claims.
|
|
1363
|
+
|
|
1364
|
+
Return exactly one JSON object and nothing else:
|
|
1365
|
+
|
|
1366
|
+
{
|
|
1367
|
+
"direction": payload.signal.direction | null,
|
|
1368
|
+
"quality": 1 | 2 | 3 | 4 | 5,
|
|
1369
|
+
"needRetest": boolean,
|
|
1370
|
+
"retestPrice": number | null,
|
|
1371
|
+
"takeProfitPrice": number | null,
|
|
1372
|
+
"stopLossPrice": number | null,
|
|
1373
|
+
"setup": string,
|
|
1374
|
+
"confirmations": string,
|
|
1375
|
+
"btcContext": string,
|
|
1376
|
+
"retestPlan": string,
|
|
1377
|
+
"riskLevels": string,
|
|
1378
|
+
"qualityReason": string,
|
|
1379
|
+
"triggerInvalidation": string
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
- Do not add any other fields.
|
|
1383
|
+
- All numbers must be finite, with no \`NaN\` or \`Infinity\`.
|
|
1384
|
+
- All text fields must be short strings with no line breaks and no markdown lists.
|
|
1385
|
+
- \`direction\` is not a new trade idea. It is only a compatibility flag for the existing signal: either exactly \`payload.signal.direction\` or \`null\` if the current structure does not confirm that signal. Never propose the opposite direction.
|
|
1386
|
+
- \`quality\` is the structural confirmation level of the current signal right now, including timing and confirmations. It is not a general attractiveness score and not investment advice.
|
|
1387
|
+
- \`needRetest\` indicates whether an additional confirmation level is required before the current signal can be treated as structurally confirmed.
|
|
1388
|
+
- \`retestPrice\` is the key level that would confirm or invalidate the structure, or \`null\` if no extra level is needed or available.
|
|
1389
|
+
- \`takeProfitPrice\` and \`stopLossPrice\` must not be newly invented levels. If the levels already supplied in \`payload.signal.prices\` still look internally coherent relative to the current price and the confirmed signal, you may return them as an audit of existing levels; otherwise return \`null\`.
|
|
1390
|
+
- Use these fields as separate parts of the analysis:
|
|
1391
|
+
- \`setup\`: the current structural setup or trendline state.
|
|
1392
|
+
- \`confirmations\`: 2-4 concrete confirmations or conflicts from the coin indicators.
|
|
1393
|
+
- \`btcContext\`: whether BTC supports the idea, is neutral, or conflicts with it.
|
|
1394
|
+
- \`retestPlan\`: what must happen at the key level to confirm the structure, or why no extra level is needed.
|
|
1395
|
+
- \`riskLevels\`: a short note on whether the existing levels and risk structure are internally coherent, without creating a new trade plan.
|
|
1396
|
+
- \`qualityReason\`: why the quality score is what it is.
|
|
1397
|
+
- \`triggerInvalidation\`: what must happen to confirm the signal or what invalidates the current structural thesis.
|
|
1398
|
+
- \`comment\` is optional. If you include it, do not just duplicate the structured fields.
|
|
1399
|
+
|
|
1400
|
+
If the data is insufficient or the setup is weak, return \`"direction": null\`, \`quality <= 2\`, and explain why.
|
|
1401
|
+
|
|
1402
|
+
Input payload structure:
|
|
1403
|
+
- payload.signal:
|
|
1404
|
+
symbol, signalId, interval, direction, timestamp, strategy, prices
|
|
1405
|
+
- payload.signal.prices:
|
|
1406
|
+
currentPrice, takeProfitPrice, stopLossPrice
|
|
1407
|
+
- payload.figures:
|
|
1408
|
+
strategy-specific figures or geometry when available. Fields vary by strategy.
|
|
1409
|
+
- payload.indicators:
|
|
1410
|
+
historical indicator dictionaries and series for the coin and BTC; all series are already trimmed to the latest 5 values. Treat this block as recent-history transport, not as the primary source of the current shared context.
|
|
1411
|
+
- payload.additionalIndicators:
|
|
1412
|
+
strategy-specific summary/context fields plus the canonical current shared context snapshot.
|
|
1413
|
+
This is not noise; it contains derived fields deliberately passed by the strategy to help the decision.
|
|
1414
|
+
Examples: baseContext, helperFlags, structureContext, volatilitySummary.
|
|
1415
|
+
Always inspect \`payload.additionalIndicators.baseContext\` first for the current shared state:
|
|
1416
|
+
\u2022 \`baseContext.raw\`: current MA, ATR, BB, OBV, price stats, levels, BTC correlation.
|
|
1417
|
+
\u2022 \`baseContext.regime\`: derived trend / volatility / momentum / session regime fields.
|
|
1418
|
+
\u2022 \`baseContext.structure\`: local range position, breakout freshness/quality, level-touch counts, rejection wick context.
|
|
1419
|
+
\u2022 \`baseContext.participation\`: volume/turnover participation, effort-vs-result context, Binance aggTrades, and fingerprinted Hyperliquid whale-flow when available.
|
|
1420
|
+
\u2022 \`baseContext.relative\`: BTC/ETH relative-strength, benchmark MA bias context, Binance alt-basket breadth, and CoinMarketCap historical global/exchange/index context when available.
|
|
1421
|
+
\u2022 \`baseContext.derivatives\`: Coinalyze-aligned derivatives summary when available.
|
|
1422
|
+
\u2022 \`baseContext.mtf\`: compact multi-timeframe summary plus only the latest few candles for each timeframe.
|
|
1423
|
+
\u2022 \`baseContext.gateFeatures\`: compact direction-aware fields derived from baseContext; prefer \`setup\`, \`scores\`, \`conflicts\`, \`risk\`, \`decisionHints\`, \`mtf\`, \`volatility\`, \`participation\`, and \`relative\` for quick gate checks before inspecting raw nested context.
|
|
1424
|
+
Always inspect \`payload.additionalIndicators.marketContext\` when present:
|
|
1425
|
+
\u2022 \`marketContext.execution.binanceCoinbaseSpread\`: AI-friendly BTC spread view projected from \`payload.additionalIndicators.baseContext.relative.execution.venueSpread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
|
|
1426
|
+
\u2022 \`marketContext.participation.trueDelta\`: Binance taker buy/sell volume delta from kline payload when \`source=kline_taker_volume\`; otherwise absent/unavailable.
|
|
1427
|
+
\u2022 \`marketContext.participation.tradeFlow\`: Binance aggTrades buy/sell pressure buckets when available.
|
|
1428
|
+
\u2022 \`marketContext.relative.marketBreadths.top5|top10|top30|top50|top100\`: equal/volume-weighted alt-basket return, advance/decline ratio, and MA breadth for the five versioned Binance breadth universes. \`marketBreadth\` remains the top30 primary view used by existing gates.
|
|
1429
|
+
\u2022 \`marketContext.relative.targetVsBtc\`: target/BTC ratio returns, alpha, beta, and short-window correlation; use it to decide whether the target is leading or lagging BTC in the signal direction.
|
|
1430
|
+
\u2022 \`marketContext.relative.btcAltRegime\`: Binance-derived BTC-vs-alt basket regime, BTC/alt 24h returns, BTC turnover share, and alt dispersion; use it as a broad alt-market risk pocket.
|
|
1431
|
+
\u2022 \`marketContext.relative.cmcGlobal\`: historical CoinMarketCap global market metrics: total/alt market cap, total/alt volume, BTC/ETH dominance and 24h changes, active markets, \`interval\`, and \`altLiquidityRegime\`.
|
|
1432
|
+
\u2022 \`marketContext.relative.cmcReferenceAssets\`: historical CoinMarketCap BTC/ETH market-cap and volume context, ETH/BTC market-cap ratio, ETH-vs-BTC volume ratio, \`interval\`, and \`referenceLiquidityRegime\`.
|
|
1433
|
+
\u2022 \`marketContext.relative.cmcExchangeLiquidity\`: historical CoinMarketCap major-exchange liquidity aggregate: total volume, 24h volume change, Binance share, concentration, and \`liquidityRegime\`.
|
|
1434
|
+
\u2022 \`marketContext.relative.cmcFearGreed\`: historical daily CoinMarketCap Fear & Greed sentiment index: value, classification, 24h/7d value changes, and \`sentimentRegime\`.
|
|
1435
|
+
\u2022 \`marketContext.relative.cmcIndexes\`: historical daily CoinMarketCap CMC100/CMC20 index values, 24h changes, top constituents, CMC20/CMC100 ratio, and \`indexRegime\`.
|
|
1436
|
+
\u2022 \`marketContext.relative.referenceTradeFlow\`: BTC/ETH reference trade-flow summary used for broad market pressure when the target symbol itself is not BTC/ETH.
|
|
1437
|
+
If those fields exist, use them as a more explicit hint instead of trying to re-derive the same idea from raw lines or points.
|
|
1438
|
+
If \`baseContext.derivatives\` exists, its top-level \`summary\` and \`intervals\` are the primary BTCUSDT Coinalyze benchmark context for the time of the signal. \`secondaryReferenceSymbol\` identifies the ETHUSDT secondary benchmark, and \`referenceContexts\` contains BTCUSDT/ETHUSDT plus configured extra reference symbols such as BNBUSDT/SOLUSDT/TRXUSDT/XRPUSDT. If \`targetContext\` or \`targetDerived\` exists, those fields are the Coinalyze context for the actual target coin; use them as target-specific positioning evidence, but do not infer target-coin derivatives when they are absent.
|
|
1439
|
+
Key patterns:
|
|
1440
|
+
\u2022 current shared state: prefer \`payload.additionalIndicators.baseContext\`
|
|
1441
|
+
\u2022 recent historical series: \`payload.indicators\`
|
|
1442
|
+
\u2022 strategy service keys are possible as well, for example \`touches\`, \`distance\`, timing flags, and other setup-specific summaries
|
|
1443
|
+
|
|
1444
|
+
How to analyze, in order:
|
|
1445
|
+
1. Start with price structure and the setup geometry or context in \`payload.figures\`. This has higher priority than indicators.
|
|
1446
|
+
2. Then use \`payload.additionalIndicators.baseContext\` and other explicit strategy-specific context fields.
|
|
1447
|
+
3. Then assess confirmation or conflict from the current shared state and recent coin indicator history.
|
|
1448
|
+
4. Then evaluate BTC context.
|
|
1449
|
+
5. Only after that choose \`direction\`, \`quality\`, and whether an extra confirmation level is required.
|
|
1450
|
+
6. If strong conflicts exist, reduce quality or set direction to \`null\`.
|
|
1451
|
+
|
|
1452
|
+
Explicit conflict rules:
|
|
1453
|
+
- If the figure or price structure is invalid or doubtful, indicators must not rescue the setup.
|
|
1454
|
+
- If strategy-specific helper fields explicitly say the signal is not confirmed yet, lacks margin, or requires waiting, do not overstate quality.
|
|
1455
|
+
- If the structure is acceptable but BTC or key indicators noticeably conflict, quality is usually \`<= 3\`.
|
|
1456
|
+
- If \`baseContext.derivatives.referenceContexts\` exists, check \`primaryReferenceSymbol\` first as the BTC benchmark, then compare \`secondaryReferenceSymbol\`/ETHUSDT and any target-specific \`targetDerived\`. If \`targetDerived\` exists, compare it to the primary reference instead of treating reference pressure as the target coin's own pressure.
|
|
1457
|
+
- If top-level \`baseContext.derivatives.summary.riskFlags\` contains \`crowded_long\` for a LONG or \`crowded_short\` for a SHORT, treat that as broad-market crowded positioning. If \`targetDerived.riskFlags\` contains the same directional crowding, treat that as target-specific crowded positioning.
|
|
1458
|
+
- If top-level \`baseContext.derivatives.summary.directionAligned=false\`, explicitly mention the broad-market derivatives conflict in \`confirmations\` or \`qualityReason\`. If \`targetDerived.directionAligned=false\`, explicitly mention the target-specific derivatives conflict.
|
|
1459
|
+
- If \`baseContext.derivatives\` is absent, stale, or \`missing_derivatives\`, do not infer Coinalyze conclusions and do not penalize the signal just because that data is missing.
|
|
1460
|
+
- Use \`baseContext.regime.session\` directly as the canonical session/liquidity regime: asia is often thinner, europe/us are more active, and overlaps can amplify both momentum and noise. Do not reject a signal solely because of session, but mention clear session support or conflict in \`confirmations\` or \`qualityReason\`.
|
|
1461
|
+
- If \`marketContext.execution.binanceCoinbaseSpread.available=true\` and \`severity=elevated/wide\`, treat it as cross-exchange divergence or BTC liquidity risk. Do not use the spread as a standalone long/short signal, but reduce confidence or require more confirmation when the rest of the structure is weak or BTC context conflicts.
|
|
1462
|
+
- If \`marketContext.execution.binanceCoinbaseSpread\` is missing or \`available=false\`, do not infer anything from Binance/Coinbase spread and do not penalize the signal just because it is absent.
|
|
1463
|
+
- If \`marketContext.participation.trueDelta.available=true\`, use it as better participation evidence than OHLCV-derived proxy delta; still do not let delta override invalid price structure.
|
|
1464
|
+
- If \`marketContext.participation.tradeFlow.available=true\` and \`stale=false\`, use it as direct lower-timeframe participation evidence. Treat stale or missing tradeFlow as absent, not as negative evidence.
|
|
1465
|
+
- If \`marketContext.relative.marketBreadth.available=true\` and \`stale=false\`, use it as broad alt-market support/conflict. Breadth is contextual; do not let it override the target symbol structure.
|
|
1466
|
+
- If \`marketContext.relative.targetVsBtc.available=true\`, treat positive target/BTC ratio trend as support for alt LONGs and negative ratio trend as support for alt SHORTs; ignore it when the target structure is stronger and clearly explains the setup.
|
|
1467
|
+
- If \`marketContext.relative.btcAltRegime.available=true\` and \`stale=false\`, treat \`alt_lead\`/\`risk_on\` as broad support for alt LONGs and \`btc_lead\`/\`risk_off\` as pressure against alt LONGs or support for cautious alt SHORTs. Do not use it as a standalone entry reason.
|
|
1468
|
+
- If \`marketContext.relative.cmcGlobal.available=true\` and \`stale=false\`, use falling alt market cap/volume or rising BTC dominance as broad risk pressure for alt LONGs. Treat missing CMC history as absent context, not a bearish signal.
|
|
1469
|
+
- If \`marketContext.relative.cmcReferenceAssets.available=true\` and \`stale=false\`, use \`eth_led\` as broad support for ETH/high-beta alt strength and \`btc_led\`/\`thin\` as broad caution. Do not describe BTC/ETH reference history as target-symbol flow.
|
|
1470
|
+
- If \`marketContext.relative.cmcExchangeLiquidity.available=true\` and \`stale=false\`, treat \`contracting\`, \`thin\`, or \`concentrated\` as broad liquidity risk; \`expanding\` or \`balanced\` supports cleaner execution context but is not a standalone entry reason.
|
|
1471
|
+
- If \`marketContext.relative.cmcFearGreed.available=true\` and \`stale=false\`, use \`risk_on\` as broad support for LONGs and \`risk_off\`/\`capitulation\` as broad pressure. Treat \`euphoric\` as overheating/chase caution, not as standalone SHORT proof.
|
|
1472
|
+
- If \`marketContext.relative.cmcIndexes.available=true\` and \`stale=false\`, use \`top20_led\` as broad support for mega-cap leadership, \`large_cap_led\` as broader CMC100 participation, and \`risk_off\` as broad pressure. Do not use CMC index history as a standalone entry reason.
|
|
1473
|
+
- If \`marketContext.relative.referenceTradeFlow.available=true\`, treat BTC/ETH trade-flow as broad market context only. For alt symbols, do not describe it as the target coin's own flow.
|
|
1474
|
+
- If the current signal is not confirmed (\`direction=null\`), name the main reason briefly in \`comment\`.
|
|
1475
|
+
If you use the structured fields, include the main reason in \`qualityReason\` or \`triggerInvalidation\`.
|
|
1476
|
+
|
|
1477
|
+
Rules for \`direction\` / TP / SL:
|
|
1478
|
+
- \`direction = LONG\` only if the data confirms the existing LONG signal; \`SHORT\` only if the data confirms the existing SHORT signal; otherwise \`null\`.
|
|
1479
|
+
- For LONG, the expected relation is usually \`stopLossPrice < currentPrice < takeProfitPrice\`.
|
|
1480
|
+
- For SHORT, the expected relation is usually \`takeProfitPrice < currentPrice < stopLossPrice\`.
|
|
1481
|
+
- Do not optimize or recalculate TP/SL for a "better trade"; only assess whether the already supplied levels are coherent.
|
|
1482
|
+
- If \`direction = null\`, then \`takeProfitPrice = null\` and \`stopLossPrice = null\`.
|
|
1483
|
+
- If \`needRetest = false\`, then \`retestPrice = null\`.
|
|
1484
|
+
- If \`needRetest = true\`, \`retestPrice\` must be a finite number tied to a meaningful retest or breakout level.
|
|
1485
|
+
- Before responding, sanity-check the consistency of \`direction\`, TP/SL, and the current price.
|
|
1486
|
+
|
|
1487
|
+
Quality scale:
|
|
1488
|
+
- 1: poor or chaotic setup, strong conflicts, signal not structurally confirmed
|
|
1489
|
+
- 2: weak setup, few confirmations, more of a watch or reject
|
|
1490
|
+
- 3: average setup, some structure exists, but notable conflicts remain
|
|
1491
|
+
- 4: good setup, several confirmations, structure is mostly coherent
|
|
1492
|
+
- 5: very strong setup, clean structure, confirmations, and internally coherent levels
|
|
1493
|
+
|
|
1494
|
+
Requirements for useful structured analysis:
|
|
1495
|
+
- Include 2-4 concrete factors for or against confirmation in \`confirmations\`.
|
|
1496
|
+
- Explicitly mention the role of the key figure or structural state, for example breakout, retest, false break, touch, or lack of confirmation.
|
|
1497
|
+
- Explicitly mention BTC context as supportive, neutral, or conflicting.
|
|
1498
|
+
- Explain why the quality score is what it is.
|
|
1499
|
+
- If the signal is not confirmed (\`direction=null\`), state clearly what must change for confirmation.
|
|
1500
|
+
- In \`retestPlan\`, avoid technical placeholders like \`needRetest=false @ null\`; write a human explanation.
|
|
1501
|
+
- Do not simply restate JSON fields; add interpretation and decision logic.
|
|
1502
|
+
|
|
1503
|
+
Rules for using trimmed series (last 5 values):
|
|
1504
|
+
- Do not make strong long-term conclusions from only 5 points.
|
|
1505
|
+
- Use 4h and 1d series as brief context, not full history.
|
|
1506
|
+
- If the data is too limited for confidence, reduce quality and use cautious wording.
|
|
1507
|
+
|
|
1508
|
+
Short few-shot examples:
|
|
1509
|
+
{"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."}
|
|
1510
|
+
{"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."}
|
|
1511
|
+
|
|
1512
|
+
Return only the JSON object, with no extra characters.
|
|
1513
|
+
${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
|
|
1514
|
+
`;
|
|
1515
|
+
var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
|
|
1516
|
+
var getDeterministicAiGateContext = (payload) => {
|
|
1517
|
+
const additionalIndicators = asRecord(payload.additionalIndicators);
|
|
1518
|
+
const candidates = [
|
|
1519
|
+
additionalIndicators,
|
|
1520
|
+
...Object.values(additionalIndicators ?? {}).map(asRecord)
|
|
1521
|
+
].filter((value) => Boolean(value));
|
|
1522
|
+
return candidates.find(
|
|
1523
|
+
(candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
|
|
1524
|
+
) ?? null;
|
|
1414
1525
|
};
|
|
1415
|
-
var
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1526
|
+
var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
|
|
1527
|
+
Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
|
|
1528
|
+
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.
|
|
1529
|
+
|
|
1530
|
+
Trade payload:
|
|
1531
|
+
${JSON.stringify(payload)}
|
|
1532
|
+
${buildAiHumanPromptAddonByStrategy(signal, payload)}
|
|
1533
|
+
`;
|
|
1534
|
+
var getAiInvocationError = (error) => {
|
|
1535
|
+
const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
|
|
1536
|
+
const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
|
|
1537
|
+
details
|
|
1538
|
+
);
|
|
1539
|
+
const wrapped = new Error(
|
|
1540
|
+
isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
|
|
1541
|
+
);
|
|
1542
|
+
wrapped.cause = error;
|
|
1543
|
+
return wrapped;
|
|
1544
|
+
};
|
|
1545
|
+
var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
|
|
1546
|
+
var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
|
|
1547
|
+
var userSettingsCache = /* @__PURE__ */ new Map();
|
|
1548
|
+
var aiModelCache = /* @__PURE__ */ new Map();
|
|
1549
|
+
var getAiModelCacheKey = (userName, modelName) => `${userName}::${modelName}`;
|
|
1550
|
+
var resolveAiModelName = (settings, requestedModelName) => {
|
|
1551
|
+
const explicitModelName = typeof requestedModelName === "string" ? requestedModelName.trim() : "";
|
|
1552
|
+
if (explicitModelName) {
|
|
1553
|
+
return explicitModelName;
|
|
1423
1554
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
configFileUrl
|
|
1427
|
-
);
|
|
1555
|
+
const settingsModelName = typeof settings.AI_MODEL === "string" ? settings.AI_MODEL.trim() : "";
|
|
1556
|
+
return settingsModelName || DEFAULT_AI_MODEL;
|
|
1428
1557
|
};
|
|
1429
|
-
var
|
|
1430
|
-
const
|
|
1431
|
-
if (!
|
|
1558
|
+
var getOpenRouterModelKwargs = (apiEndpoint) => {
|
|
1559
|
+
const endpoint = String(apiEndpoint ?? "").trim();
|
|
1560
|
+
if (!endpoint) {
|
|
1432
1561
|
return {};
|
|
1433
1562
|
}
|
|
1434
|
-
let
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
modulePath = normalized;
|
|
1440
|
-
}
|
|
1563
|
+
let hostname = "";
|
|
1564
|
+
try {
|
|
1565
|
+
hostname = new URL(endpoint).hostname;
|
|
1566
|
+
} catch {
|
|
1567
|
+
hostname = endpoint;
|
|
1441
1568
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
);
|
|
1445
|
-
if (isTsModulePath(modulePath)) {
|
|
1446
|
-
await ensureTsNodeRegistered();
|
|
1447
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
1448
|
-
return requireFn(modulePath);
|
|
1569
|
+
if (!hostname.toLowerCase().includes("openrouter")) {
|
|
1570
|
+
return {};
|
|
1449
1571
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
return requireFn(normalized);
|
|
1454
|
-
} catch (error) {
|
|
1455
|
-
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
1456
|
-
normalized,
|
|
1457
|
-
cwd
|
|
1458
|
-
);
|
|
1459
|
-
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
1460
|
-
return requireFn(resolvedByTsconfig);
|
|
1461
|
-
}
|
|
1462
|
-
throw error;
|
|
1572
|
+
return {
|
|
1573
|
+
provider: {
|
|
1574
|
+
ignore: ["azure"]
|
|
1463
1575
|
}
|
|
1576
|
+
};
|
|
1577
|
+
};
|
|
1578
|
+
var getAiSettings = async (userName = "root") => {
|
|
1579
|
+
let settingsPromise = userSettingsCache.get(userName);
|
|
1580
|
+
if (!settingsPromise) {
|
|
1581
|
+
settingsPromise = (0, import_userSettings.getUserSettings)(userName).then((settings2) => {
|
|
1582
|
+
const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
|
|
1583
|
+
return {
|
|
1584
|
+
...settings2,
|
|
1585
|
+
AI_API_ENDPOINT: endpoint,
|
|
1586
|
+
AI_MODEL: (0, import_aiModels.normalizeAiModel)(settings2.AI_MODEL, endpoint),
|
|
1587
|
+
AI_RESPONSE_LANGUAGE: (0, import_aiLanguages.normalizeAiResponseLanguage)(
|
|
1588
|
+
settings2.AI_RESPONSE_LANGUAGE
|
|
1589
|
+
)
|
|
1590
|
+
};
|
|
1591
|
+
});
|
|
1592
|
+
settingsPromise.catch(() => {
|
|
1593
|
+
userSettingsCache.delete(userName);
|
|
1594
|
+
});
|
|
1595
|
+
userSettingsCache.set(userName, settingsPromise);
|
|
1464
1596
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
toImportSpecifier(normalized)
|
|
1469
|
-
);
|
|
1470
|
-
} catch (error) {
|
|
1471
|
-
if (isTsModulePath(modulePath)) {
|
|
1472
|
-
await ensureTsNodeRegistered();
|
|
1473
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
1474
|
-
return requireFn(modulePath);
|
|
1475
|
-
}
|
|
1476
|
-
throw error;
|
|
1597
|
+
const settings = await settingsPromise;
|
|
1598
|
+
if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
|
|
1599
|
+
throw new Error(`AI settings are incomplete for user ${userName}`);
|
|
1477
1600
|
}
|
|
1601
|
+
return settings;
|
|
1478
1602
|
};
|
|
1479
|
-
var
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1603
|
+
var createAiModel = async (userName = "root", requestedModelName) => {
|
|
1604
|
+
const settings = await getAiSettings(userName);
|
|
1605
|
+
const modelName = resolveAiModelName(settings, requestedModelName);
|
|
1606
|
+
const cacheKey = getAiModelCacheKey(userName, modelName);
|
|
1607
|
+
let modelPromise = aiModelCache.get(cacheKey);
|
|
1608
|
+
if (!modelPromise) {
|
|
1609
|
+
modelPromise = (async () => {
|
|
1610
|
+
const { ChatOpenAI } = await import("@langchain/openai");
|
|
1611
|
+
const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
|
|
1612
|
+
return new ChatOpenAI({
|
|
1613
|
+
temperature: 0.2,
|
|
1614
|
+
modelName,
|
|
1615
|
+
apiKey: settings.AI_API_KEY,
|
|
1616
|
+
...Object.keys(modelKwargs).length ? { modelKwargs } : {},
|
|
1617
|
+
configuration: {
|
|
1618
|
+
baseURL: settings.AI_API_ENDPOINT,
|
|
1619
|
+
defaultHeaders: {
|
|
1620
|
+
"HTTP-Referer": "https://tradejs.dev",
|
|
1621
|
+
"X-Title": "Inv"
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
})();
|
|
1626
|
+
modelPromise.catch(() => {
|
|
1627
|
+
aiModelCache.delete(cacheKey);
|
|
1628
|
+
});
|
|
1629
|
+
aiModelCache.set(cacheKey, modelPromise);
|
|
1630
|
+
}
|
|
1631
|
+
return modelPromise;
|
|
1482
1632
|
};
|
|
1483
|
-
var
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
const parentDir = import_path.default.dirname(currentDir);
|
|
1493
|
-
if (parentDir === currentDir) {
|
|
1494
|
-
return null;
|
|
1495
|
-
}
|
|
1496
|
-
currentDir = parentDir;
|
|
1633
|
+
var getAiModel = async (userName = "root", requestedModelName) => {
|
|
1634
|
+
const settings = await getAiSettings(userName);
|
|
1635
|
+
const resolvedModelName = resolveAiModelName(settings, requestedModelName);
|
|
1636
|
+
try {
|
|
1637
|
+
return await createAiModel(userName, resolvedModelName);
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
aiModelCache.delete(getAiModelCacheKey(userName, resolvedModelName));
|
|
1640
|
+
userSettingsCache.delete(userName);
|
|
1641
|
+
throw error;
|
|
1497
1642
|
}
|
|
1498
1643
|
};
|
|
1499
|
-
var
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
|
|
1644
|
+
var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
1645
|
+
const [{ HumanMessage, SystemMessage }, model, settings] = await Promise.all([
|
|
1646
|
+
import("@langchain/core/messages"),
|
|
1647
|
+
getAiModel(options.userName, options.model),
|
|
1648
|
+
getAiSettings(options.userName)
|
|
1649
|
+
]);
|
|
1650
|
+
const messages = [];
|
|
1651
|
+
const responseLanguage = (0, import_aiLanguages.getAiResponseLanguagePromptName)(
|
|
1652
|
+
settings.AI_RESPONSE_LANGUAGE || import_aiLanguages.DEFAULT_AI_RESPONSE_LANGUAGE
|
|
1653
|
+
);
|
|
1654
|
+
messages.push(new SystemMessage(systemPrompt));
|
|
1655
|
+
messages.push(
|
|
1656
|
+
new SystemMessage(
|
|
1657
|
+
`Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
1658
|
+
)
|
|
1659
|
+
);
|
|
1660
|
+
messages.push(
|
|
1661
|
+
new HumanMessage({
|
|
1662
|
+
content: [
|
|
1663
|
+
{
|
|
1664
|
+
type: "text",
|
|
1665
|
+
text: humanPrompt
|
|
1666
|
+
}
|
|
1667
|
+
]
|
|
1668
|
+
})
|
|
1669
|
+
);
|
|
1670
|
+
let response;
|
|
1671
|
+
try {
|
|
1672
|
+
response = await model.invoke(messages);
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
throw getAiInvocationError(error);
|
|
1503
1675
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
} catch {
|
|
1508
|
-
return normalized;
|
|
1509
|
-
}
|
|
1676
|
+
const responseContent = normalizeResponseContent(response?.content);
|
|
1677
|
+
if (isEmptyResponseContent(responseContent)) {
|
|
1678
|
+
throw new Error("AI provider returned an empty chat completion");
|
|
1510
1679
|
}
|
|
1511
|
-
|
|
1680
|
+
const parsed = parseAIResponse(responseContent);
|
|
1681
|
+
const normalized = normalizeAnalysis(parsed);
|
|
1682
|
+
if (!options.signal) {
|
|
1512
1683
|
return normalized;
|
|
1513
1684
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1685
|
+
return postProcessAiAnalysisByStrategy(
|
|
1686
|
+
options.signal,
|
|
1687
|
+
normalized,
|
|
1688
|
+
options.payload
|
|
1689
|
+
);
|
|
1690
|
+
};
|
|
1691
|
+
var runAiPromptLocal = async (signal, options = {}) => {
|
|
1692
|
+
const payload = options.payload ?? buildAiPayload(signal);
|
|
1693
|
+
const gateContext = getDeterministicAiGateContext(payload);
|
|
1694
|
+
const signalDirection = getSignalDirection(signal);
|
|
1695
|
+
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
1696
|
+
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
1697
|
+
return postProcessLocalAiAnalysisByStrategy(
|
|
1698
|
+
signal,
|
|
1699
|
+
{
|
|
1700
|
+
direction: approvalAllowedNow ? signalDirection : null,
|
|
1701
|
+
quality: deterministicQuality,
|
|
1702
|
+
needRetest: !approvalAllowedNow,
|
|
1703
|
+
retestPrice: null,
|
|
1704
|
+
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
1705
|
+
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
1706
|
+
},
|
|
1707
|
+
payload
|
|
1708
|
+
);
|
|
1709
|
+
};
|
|
1710
|
+
var askAI = async (signal, options = {}) => {
|
|
1711
|
+
const { symbol } = signal;
|
|
1712
|
+
const payload = buildAiPayload(signal);
|
|
1713
|
+
const content = await runAiPrompt(
|
|
1714
|
+
{
|
|
1715
|
+
systemPrompt: buildAiSystemPrompt(signal),
|
|
1716
|
+
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
1717
|
+
},
|
|
1718
|
+
{
|
|
1719
|
+
...options,
|
|
1720
|
+
signal,
|
|
1721
|
+
payload
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
1725
|
+
return content;
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// src/strategyAdapters/ml.ts
|
|
1729
|
+
var defaultMlAdapter = {
|
|
1730
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
1731
|
+
};
|
|
1732
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
1733
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
1734
|
+
getStrategyManifest(strategy),
|
|
1735
|
+
profileId
|
|
1736
|
+
);
|
|
1737
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
1738
|
+
return {
|
|
1739
|
+
...defaultMlAdapter,
|
|
1740
|
+
...strategyAdapter
|
|
1741
|
+
};
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
// src/mlPayload.ts
|
|
1745
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
1746
|
+
return getStrategyMlAdapter(
|
|
1747
|
+
strategyName,
|
|
1748
|
+
profileId
|
|
1749
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
1518
1750
|
};
|
|
1519
|
-
var
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
const
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
}
|
|
1529
|
-
try {
|
|
1530
|
-
const moduleExports = await importConfigFile(configFilePath);
|
|
1531
|
-
const config = resolveExportedConfig(moduleExports);
|
|
1532
|
-
cachedByCwd.set(cwd, config);
|
|
1533
|
-
if (!announcedConfigFile.has(configFilePath)) {
|
|
1534
|
-
announcedConfigFile.add(configFilePath);
|
|
1535
|
-
import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
1751
|
+
var buildMlPayload = (payload) => {
|
|
1752
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
1753
|
+
const profileId = payload.signal?.policyProfileId;
|
|
1754
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
1755
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
1756
|
+
const nextSignal = {
|
|
1757
|
+
...normalizedSignal,
|
|
1758
|
+
indicators: {
|
|
1759
|
+
...normalizedSignal?.indicators ?? {}
|
|
1536
1760
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
)
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1761
|
+
};
|
|
1762
|
+
const nextContext = payload.context ? {
|
|
1763
|
+
...payload.context,
|
|
1764
|
+
strategyConfig: normalizeStrategyConfig(
|
|
1765
|
+
payload.context.strategyConfig,
|
|
1766
|
+
strategyName,
|
|
1767
|
+
profileId
|
|
1768
|
+
)
|
|
1769
|
+
} : void 0;
|
|
1770
|
+
return {
|
|
1771
|
+
signal: nextSignal,
|
|
1772
|
+
context: nextContext
|
|
1773
|
+
};
|
|
1548
1774
|
};
|
|
1549
1775
|
|
|
1550
1776
|
// src/runtimeJournal.ts
|
|
@@ -1552,7 +1778,7 @@ var import_node_crypto = require("crypto");
|
|
|
1552
1778
|
var import_constants = require("@tradejs/core/constants");
|
|
1553
1779
|
var import_time = require("@tradejs/core/time");
|
|
1554
1780
|
var import_trade = require("@tradejs/core/trade");
|
|
1555
|
-
var
|
|
1781
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
1556
1782
|
var import_redis2 = require("@tradejs/infra/redis");
|
|
1557
1783
|
var now = () => Date.now();
|
|
1558
1784
|
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
@@ -1611,7 +1837,7 @@ var recordRuntimeTradeOpen = async (params) => {
|
|
|
1611
1837
|
)
|
|
1612
1838
|
]);
|
|
1613
1839
|
} catch (error) {
|
|
1614
|
-
|
|
1840
|
+
import_logger3.logger.error(
|
|
1615
1841
|
"runtime trade open journal failed: %s %s",
|
|
1616
1842
|
record.symbol,
|
|
1617
1843
|
error?.message || String(error)
|
|
@@ -1678,7 +1904,7 @@ var recordRuntimeTradeIncrease = async (params) => {
|
|
|
1678
1904
|
)
|
|
1679
1905
|
]);
|
|
1680
1906
|
} catch (error) {
|
|
1681
|
-
|
|
1907
|
+
import_logger3.logger.error(
|
|
1682
1908
|
"runtime trade increase journal failed: %s %s",
|
|
1683
1909
|
symbol,
|
|
1684
1910
|
error?.message || String(error)
|
|
@@ -1785,7 +2011,7 @@ var markRuntimeTradeClosed = async (params) => {
|
|
|
1785
2011
|
)
|
|
1786
2012
|
]);
|
|
1787
2013
|
} catch (error) {
|
|
1788
|
-
|
|
2014
|
+
import_logger3.logger.error(
|
|
1789
2015
|
"runtime trade close journal failed: %s %s",
|
|
1790
2016
|
symbol,
|
|
1791
2017
|
error?.message || String(error)
|
|
@@ -1795,11 +2021,11 @@ var markRuntimeTradeClosed = async (params) => {
|
|
|
1795
2021
|
};
|
|
1796
2022
|
|
|
1797
2023
|
// src/strategyHelpers/marketContextStages.ts
|
|
1798
|
-
var
|
|
2024
|
+
var import_logger8 = require("@tradejs/infra/logger");
|
|
1799
2025
|
|
|
1800
2026
|
// src/strategyHelpers/binanceMarketContext.ts
|
|
1801
2027
|
var import_marketContext = require("@tradejs/infra/timescale/marketContext");
|
|
1802
|
-
var
|
|
2028
|
+
var import_logger4 = require("@tradejs/infra/logger");
|
|
1803
2029
|
var import_strategies = require("@tradejs/core/strategies");
|
|
1804
2030
|
|
|
1805
2031
|
// src/binanceBreadthUniverses.ts
|
|
@@ -2395,7 +2621,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
|
2395
2621
|
throw error;
|
|
2396
2622
|
}
|
|
2397
2623
|
binanceMarketContextUnavailable = true;
|
|
2398
|
-
|
|
2624
|
+
import_logger4.logger.warn(
|
|
2399
2625
|
"Binance market context disabled after Timescale read failure: %s",
|
|
2400
2626
|
String(error)
|
|
2401
2627
|
);
|
|
@@ -2405,7 +2631,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
|
2405
2631
|
|
|
2406
2632
|
// src/strategyHelpers/coinMarketCapContext.ts
|
|
2407
2633
|
var import_strategies2 = require("@tradejs/core/strategies");
|
|
2408
|
-
var
|
|
2634
|
+
var import_logger5 = require("@tradejs/infra/logger");
|
|
2409
2635
|
var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
|
|
2410
2636
|
var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
|
|
2411
2637
|
var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
|
|
@@ -2894,7 +3120,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
2894
3120
|
throw error;
|
|
2895
3121
|
}
|
|
2896
3122
|
coinMarketCapContextUnavailable = true;
|
|
2897
|
-
|
|
3123
|
+
import_logger5.logger.warn(
|
|
2898
3124
|
"CoinMarketCap context disabled after Timescale read failure: %s",
|
|
2899
3125
|
String(error)
|
|
2900
3126
|
);
|
|
@@ -2903,12 +3129,12 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
2903
3129
|
};
|
|
2904
3130
|
|
|
2905
3131
|
// src/strategyHelpers/derivativesContext.ts
|
|
2906
|
-
var
|
|
3132
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
2907
3133
|
var import_data = require("@tradejs/core/data");
|
|
2908
3134
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
2909
3135
|
var import_constants2 = require("@tradejs/core/constants");
|
|
2910
3136
|
var import_derivatives = require("@tradejs/infra/timescale/derivatives");
|
|
2911
|
-
var
|
|
3137
|
+
var import_logger6 = require("@tradejs/infra/logger");
|
|
2912
3138
|
var STORED_INTERVALS = ["15m", "1h"];
|
|
2913
3139
|
var CONTEXT_INTERVALS = ["15m", "1h"];
|
|
2914
3140
|
var DEFAULT_LOOKBACK_HOURS = 48;
|
|
@@ -2937,7 +3163,7 @@ var parseLookbackMs = () => {
|
|
|
2937
3163
|
};
|
|
2938
3164
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
2939
3165
|
"15m": rowsByInterval["15m"] ?? [],
|
|
2940
|
-
"1h": (0,
|
|
3166
|
+
"1h": (0, import_indicators2.buildCoinalyzeHourlyRowsWithFallback)({
|
|
2941
3167
|
rows15m: rowsByInterval["15m"],
|
|
2942
3168
|
fallbackRows1h: rowsByInterval["1h"]
|
|
2943
3169
|
})
|
|
@@ -3065,7 +3291,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
3065
3291
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
3066
3292
|
const lookbackMs = parseLookbackMs();
|
|
3067
3293
|
const decisionTimeMs = signal.timestamp + (0, import_data.intervalToMs)(signal.interval);
|
|
3068
|
-
const derivativesEndMs = (0,
|
|
3294
|
+
const derivativesEndMs = (0, import_indicators2.getLastClosedDerivativesBarStartMs)(
|
|
3069
3295
|
decisionTimeMs,
|
|
3070
3296
|
"15m"
|
|
3071
3297
|
);
|
|
@@ -3080,7 +3306,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
3080
3306
|
});
|
|
3081
3307
|
return [
|
|
3082
3308
|
symbol,
|
|
3083
|
-
(0,
|
|
3309
|
+
(0, import_indicators2.buildDerivativesContext)({
|
|
3084
3310
|
symbol,
|
|
3085
3311
|
direction: signal.direction,
|
|
3086
3312
|
timestamp: derivativesEndMs,
|
|
@@ -3107,7 +3333,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
3107
3333
|
lookbackMs,
|
|
3108
3334
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
3109
3335
|
});
|
|
3110
|
-
const context = (0,
|
|
3336
|
+
const context = (0, import_indicators2.buildDerivativesContext)({
|
|
3111
3337
|
symbol: targetSymbol,
|
|
3112
3338
|
direction: signal.direction,
|
|
3113
3339
|
timestamp: derivativesEndMs,
|
|
@@ -3140,7 +3366,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
3140
3366
|
throw error;
|
|
3141
3367
|
}
|
|
3142
3368
|
derivativesContextUnavailable = true;
|
|
3143
|
-
|
|
3369
|
+
import_logger6.logger.warn(
|
|
3144
3370
|
"Derivatives context disabled after Timescale read failure: %s",
|
|
3145
3371
|
String(error)
|
|
3146
3372
|
);
|
|
@@ -3152,7 +3378,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
3152
3378
|
var import_data2 = require("@tradejs/core/data");
|
|
3153
3379
|
var import_strategies4 = require("@tradejs/core/strategies");
|
|
3154
3380
|
var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
|
|
3155
|
-
var
|
|
3381
|
+
var import_logger7 = require("@tradejs/infra/logger");
|
|
3156
3382
|
|
|
3157
3383
|
// src/hyperliquidWhaleUniverse.ts
|
|
3158
3384
|
var import_node_crypto3 = require("crypto");
|
|
@@ -3672,7 +3898,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
|
|
|
3672
3898
|
throw error;
|
|
3673
3899
|
}
|
|
3674
3900
|
hyperliquidWhaleContextUnavailable = true;
|
|
3675
|
-
|
|
3901
|
+
import_logger7.logger.warn(
|
|
3676
3902
|
"Hyperliquid whale context disabled after Timescale read failure: %s",
|
|
3677
3903
|
String(error)
|
|
3678
3904
|
);
|
|
@@ -3755,7 +3981,7 @@ var runMarketContextStage = async ({
|
|
|
3755
3981
|
elapsedMs: Date.now() - startedAt
|
|
3756
3982
|
};
|
|
3757
3983
|
if (status === "timed_out") {
|
|
3758
|
-
|
|
3984
|
+
import_logger8.logger.warn(
|
|
3759
3985
|
"Market context stage timed out: %s after %sms",
|
|
3760
3986
|
stage,
|
|
3761
3987
|
result.elapsedMs
|
|
@@ -3945,7 +4171,7 @@ var enrichSignalWithAi = async ({
|
|
|
3945
4171
|
signal.aiAnalysis = analysis;
|
|
3946
4172
|
return resolveAiQuality(analysis, direction);
|
|
3947
4173
|
} catch (err) {
|
|
3948
|
-
|
|
4174
|
+
import_logger9.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
|
|
3949
4175
|
}
|
|
3950
4176
|
return void 0;
|
|
3951
4177
|
};
|
|
@@ -3980,7 +4206,7 @@ var getOrderArrivalSnapshot = async ({
|
|
|
3980
4206
|
spreadBps
|
|
3981
4207
|
};
|
|
3982
4208
|
} catch (error) {
|
|
3983
|
-
|
|
4209
|
+
import_logger9.logger.warn(
|
|
3984
4210
|
"runtime order arrival snapshot failed: %s %s",
|
|
3985
4211
|
symbol,
|
|
3986
4212
|
error?.message || String(error)
|
|
@@ -4773,7 +4999,7 @@ var canUseSharedReplayState = ({
|
|
|
4773
4999
|
}) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
|
|
4774
5000
|
|
|
4775
5001
|
// src/strategy/runtimeExecution.ts
|
|
4776
|
-
var
|
|
5002
|
+
var import_logger10 = require("@tradejs/infra/logger");
|
|
4777
5003
|
var import_types = require("@tradejs/types");
|
|
4778
5004
|
var buildExitOrderSignal = ({
|
|
4779
5005
|
strategyName,
|
|
@@ -4825,7 +5051,7 @@ var handleExitDecision = async ({
|
|
|
4825
5051
|
deploymentId: connector.deploymentId
|
|
4826
5052
|
});
|
|
4827
5053
|
if (!activeTrade) {
|
|
4828
|
-
|
|
5054
|
+
import_logger10.logger.warn(
|
|
4829
5055
|
"[%s] blocked closePosition for untracked runtime position: %s",
|
|
4830
5056
|
strategyName ?? "unknown",
|
|
4831
5057
|
symbol
|
|
@@ -4833,7 +5059,7 @@ var handleExitDecision = async ({
|
|
|
4833
5059
|
return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
|
|
4834
5060
|
}
|
|
4835
5061
|
if (!strategyName || activeTrade.strategy !== strategyName) {
|
|
4836
|
-
|
|
5062
|
+
import_logger10.logger.warn(
|
|
4837
5063
|
"[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
|
|
4838
5064
|
strategyName ?? "unknown",
|
|
4839
5065
|
symbol,
|
|
@@ -4885,7 +5111,7 @@ var handleExitDecision = async ({
|
|
|
4885
5111
|
exitType: closedTrade?.exitType ?? "exit"
|
|
4886
5112
|
});
|
|
4887
5113
|
} catch (notificationError) {
|
|
4888
|
-
|
|
5114
|
+
import_logger10.logger.error(
|
|
4889
5115
|
"runtime close notification error: %s %s",
|
|
4890
5116
|
symbol,
|
|
4891
5117
|
notificationError
|
|
@@ -4899,7 +5125,7 @@ var handleExitDecision = async ({
|
|
|
4899
5125
|
decision,
|
|
4900
5126
|
market
|
|
4901
5127
|
});
|
|
4902
|
-
|
|
5128
|
+
import_logger10.logger.error("close order error: %s %s", symbol, err);
|
|
4903
5129
|
return "ORDER_ERROR";
|
|
4904
5130
|
}
|
|
4905
5131
|
return decision.code;
|
|
@@ -4926,7 +5152,7 @@ var handleProtectDecision = async ({
|
|
|
4926
5152
|
decision,
|
|
4927
5153
|
market
|
|
4928
5154
|
});
|
|
4929
|
-
|
|
5155
|
+
import_logger10.logger.error("protect position error: %s %s", symbol, err);
|
|
4930
5156
|
return "ORDER_ERROR";
|
|
4931
5157
|
}
|
|
4932
5158
|
return decision.code;
|
|
@@ -5089,9 +5315,9 @@ var executeEntryDecision = async ({
|
|
|
5089
5315
|
market
|
|
5090
5316
|
});
|
|
5091
5317
|
if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
|
|
5092
|
-
|
|
5318
|
+
import_logger10.logger.warn("order warning: %s %s", symbol, err);
|
|
5093
5319
|
} else {
|
|
5094
|
-
|
|
5320
|
+
import_logger10.logger.error("order error: %s %s", symbol, err);
|
|
5095
5321
|
}
|
|
5096
5322
|
return signal ?? "ORDER_ERROR";
|
|
5097
5323
|
}
|
|
@@ -5243,7 +5469,7 @@ var createStrategyRuntime = ({
|
|
|
5243
5469
|
try {
|
|
5244
5470
|
await projectHook(errorParams);
|
|
5245
5471
|
} catch (hookError) {
|
|
5246
|
-
|
|
5472
|
+
import_logger11.logger.error(
|
|
5247
5473
|
"project hook onRuntimeError failed: %s %s",
|
|
5248
5474
|
strategyName,
|
|
5249
5475
|
hookError
|
|
@@ -5257,7 +5483,7 @@ var createStrategyRuntime = ({
|
|
|
5257
5483
|
try {
|
|
5258
5484
|
await onRuntimeError(errorParams);
|
|
5259
5485
|
} catch (hookError) {
|
|
5260
|
-
|
|
5486
|
+
import_logger11.logger.error(
|
|
5261
5487
|
"runtime hook onRuntimeError failed: %s %s",
|
|
5262
5488
|
strategyName,
|
|
5263
5489
|
hookError
|
|
@@ -5271,7 +5497,7 @@ var createStrategyRuntime = ({
|
|
|
5271
5497
|
try {
|
|
5272
5498
|
return await hook(params);
|
|
5273
5499
|
} catch (error) {
|
|
5274
|
-
|
|
5500
|
+
import_logger11.logger.error(
|
|
5275
5501
|
'strategy hook "%s" failed for %s: %s',
|
|
5276
5502
|
stage,
|
|
5277
5503
|
strategyName,
|
|
@@ -6011,204 +6237,8 @@ var createStrategyRuntime = ({
|
|
|
6011
6237
|
return creator;
|
|
6012
6238
|
};
|
|
6013
6239
|
|
|
6014
|
-
// src/strategy/
|
|
6015
|
-
|
|
6016
|
-
strategyCreators: /* @__PURE__ */ new Map(),
|
|
6017
|
-
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
6018
|
-
pluginsLoadPromise: null
|
|
6019
|
-
});
|
|
6020
|
-
var registryStateByProjectRoot = /* @__PURE__ */ new Map();
|
|
6021
|
-
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
6022
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
6023
|
-
let state = registryStateByProjectRoot.get(projectRoot);
|
|
6024
|
-
if (!state) {
|
|
6025
|
-
state = createStrategyRegistryState();
|
|
6026
|
-
registryStateByProjectRoot.set(projectRoot, state);
|
|
6027
|
-
}
|
|
6028
|
-
return {
|
|
6029
|
-
projectRoot,
|
|
6030
|
-
state
|
|
6031
|
-
};
|
|
6032
|
-
};
|
|
6033
|
-
var toUniqueModules = (modules = []) => [
|
|
6034
|
-
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
6035
|
-
];
|
|
6036
|
-
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
6037
|
-
const config = await loadTradejsConfig(cwd);
|
|
6038
|
-
return {
|
|
6039
|
-
strategyModules: toUniqueModules(config.strategies),
|
|
6040
|
-
indicatorModules: toUniqueModules(config.indicators)
|
|
6041
|
-
};
|
|
6042
|
-
};
|
|
6043
|
-
var extractModuleEntries = (moduleExport, key) => {
|
|
6044
|
-
if (!moduleExport || typeof moduleExport !== "object") {
|
|
6045
|
-
return null;
|
|
6046
|
-
}
|
|
6047
|
-
const candidate = moduleExport;
|
|
6048
|
-
if (Array.isArray(candidate[key])) {
|
|
6049
|
-
return candidate[key];
|
|
6050
|
-
}
|
|
6051
|
-
const defaultExport = candidate.default;
|
|
6052
|
-
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
6053
|
-
return defaultExport[key];
|
|
6054
|
-
}
|
|
6055
|
-
return null;
|
|
6056
|
-
};
|
|
6057
|
-
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
6058
|
-
const strategyEntries = extractModuleEntries(
|
|
6059
|
-
moduleExport,
|
|
6060
|
-
"strategyEntries"
|
|
6061
|
-
);
|
|
6062
|
-
return strategyEntries ? { strategyEntries } : null;
|
|
6063
|
-
};
|
|
6064
|
-
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
6065
|
-
const indicatorEntries = extractModuleEntries(
|
|
6066
|
-
moduleExport,
|
|
6067
|
-
"indicatorEntries"
|
|
6068
|
-
);
|
|
6069
|
-
return indicatorEntries ? { indicatorEntries } : null;
|
|
6070
|
-
};
|
|
6071
|
-
var registerEntries = (entries, source, state) => {
|
|
6072
|
-
for (const entry of entries) {
|
|
6073
|
-
const strategyName = entry.manifest?.name;
|
|
6074
|
-
if (!strategyName) {
|
|
6075
|
-
import_logger11.logger.warn("Skip strategy entry without name from %s", source);
|
|
6076
|
-
continue;
|
|
6077
|
-
}
|
|
6078
|
-
if (state.strategyCreators.has(strategyName)) {
|
|
6079
|
-
import_logger11.logger.warn(
|
|
6080
|
-
'Skip duplicate strategy "%s" from %s: already registered',
|
|
6081
|
-
strategyName,
|
|
6082
|
-
source
|
|
6083
|
-
);
|
|
6084
|
-
continue;
|
|
6085
|
-
}
|
|
6086
|
-
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
6087
|
-
state.strategyCreators.set(
|
|
6088
|
-
strategyName,
|
|
6089
|
-
createStrategyRuntime({
|
|
6090
|
-
strategyName,
|
|
6091
|
-
defaults: entry.defaults,
|
|
6092
|
-
createCore: entry.createCore,
|
|
6093
|
-
manifest: entry.manifest,
|
|
6094
|
-
detectorKey: entry.detectorKey,
|
|
6095
|
-
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
6096
|
-
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
6097
|
-
})
|
|
6098
|
-
);
|
|
6099
|
-
}
|
|
6100
|
-
};
|
|
6101
|
-
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
6102
|
-
if (typeof importTradejsModule === "function") {
|
|
6103
|
-
return importTradejsModule(moduleName, cwd);
|
|
6104
|
-
}
|
|
6105
|
-
return import(
|
|
6106
|
-
/* webpackIgnore: true */
|
|
6107
|
-
moduleName
|
|
6108
|
-
);
|
|
6109
|
-
};
|
|
6110
|
-
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
6111
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
6112
|
-
if (!state.pluginsLoadPromise) {
|
|
6113
|
-
(0, import_indicators2.resetIndicatorRegistryCache)(projectRoot);
|
|
6114
|
-
state.pluginsLoadPromise = (async () => {
|
|
6115
|
-
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
6116
|
-
const strategySet = new Set(strategyModules);
|
|
6117
|
-
const indicatorSet = new Set(indicatorModules);
|
|
6118
|
-
const pluginModuleNames = [
|
|
6119
|
-
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
6120
|
-
];
|
|
6121
|
-
if (!pluginModuleNames.length) {
|
|
6122
|
-
return;
|
|
6123
|
-
}
|
|
6124
|
-
for (const moduleName of pluginModuleNames) {
|
|
6125
|
-
try {
|
|
6126
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
6127
|
-
moduleName,
|
|
6128
|
-
projectRoot
|
|
6129
|
-
);
|
|
6130
|
-
const moduleExport = await importStrategyPluginModule(
|
|
6131
|
-
resolvedModuleName,
|
|
6132
|
-
projectRoot
|
|
6133
|
-
);
|
|
6134
|
-
if (strategySet.has(moduleName)) {
|
|
6135
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
6136
|
-
if (!pluginDefinition) {
|
|
6137
|
-
import_logger11.logger.warn(
|
|
6138
|
-
'Skip strategy plugin "%s": export { strategyEntries } is missing',
|
|
6139
|
-
moduleName
|
|
6140
|
-
);
|
|
6141
|
-
} else {
|
|
6142
|
-
registerEntries(
|
|
6143
|
-
pluginDefinition.strategyEntries,
|
|
6144
|
-
moduleName,
|
|
6145
|
-
state
|
|
6146
|
-
);
|
|
6147
|
-
}
|
|
6148
|
-
}
|
|
6149
|
-
if (indicatorSet.has(moduleName)) {
|
|
6150
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
6151
|
-
if (!indicatorPluginDefinition) {
|
|
6152
|
-
import_logger11.logger.warn(
|
|
6153
|
-
'Skip indicator plugin "%s": export { indicatorEntries } is missing',
|
|
6154
|
-
moduleName
|
|
6155
|
-
);
|
|
6156
|
-
} else {
|
|
6157
|
-
(0, import_indicators2.registerIndicatorEntries)(
|
|
6158
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
6159
|
-
moduleName,
|
|
6160
|
-
projectRoot
|
|
6161
|
-
);
|
|
6162
|
-
}
|
|
6163
|
-
}
|
|
6164
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
6165
|
-
import_logger11.logger.warn(
|
|
6166
|
-
'Skip plugin "%s": no strategy/indicator sections requested in config',
|
|
6167
|
-
moduleName
|
|
6168
|
-
);
|
|
6169
|
-
}
|
|
6170
|
-
} catch (error) {
|
|
6171
|
-
import_logger11.logger.warn(
|
|
6172
|
-
'Failed to load plugin "%s": %s',
|
|
6173
|
-
moduleName,
|
|
6174
|
-
String(error)
|
|
6175
|
-
);
|
|
6176
|
-
}
|
|
6177
|
-
}
|
|
6178
|
-
})();
|
|
6179
|
-
}
|
|
6180
|
-
await state.pluginsLoadPromise;
|
|
6181
|
-
};
|
|
6182
|
-
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd()) => {
|
|
6183
|
-
await ensureStrategyPluginsLoaded(cwd);
|
|
6184
|
-
const { state } = getStrategyRegistryState(cwd);
|
|
6185
|
-
return state.strategyCreators.get(name);
|
|
6186
|
-
};
|
|
6187
|
-
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
6188
|
-
if (!name) {
|
|
6189
|
-
return void 0;
|
|
6190
|
-
}
|
|
6191
|
-
const { state } = getStrategyRegistryState(cwd);
|
|
6192
|
-
return state.strategyManifestsMap.get(name);
|
|
6193
|
-
};
|
|
6194
|
-
var strategies = new Proxy(
|
|
6195
|
-
{},
|
|
6196
|
-
{
|
|
6197
|
-
get: (_target, property) => {
|
|
6198
|
-
if (typeof property !== "string") {
|
|
6199
|
-
return void 0;
|
|
6200
|
-
}
|
|
6201
|
-
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
6202
|
-
},
|
|
6203
|
-
ownKeys: () => {
|
|
6204
|
-
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
6205
|
-
},
|
|
6206
|
-
getOwnPropertyDescriptor: () => ({
|
|
6207
|
-
enumerable: true,
|
|
6208
|
-
configurable: true
|
|
6209
|
-
})
|
|
6210
|
-
}
|
|
6211
|
-
);
|
|
6240
|
+
// src/strategy/index.ts
|
|
6241
|
+
setStrategyRuntimeFactory(createStrategyRuntime);
|
|
6212
6242
|
|
|
6213
6243
|
// src/connectorsRegistry.ts
|
|
6214
6244
|
var import_logger12 = require("@tradejs/infra/logger");
|
|
@@ -6695,6 +6725,7 @@ var createTestConnector = (connector, context) => {
|
|
|
6695
6725
|
};
|
|
6696
6726
|
const createOpenTradeResult = ({
|
|
6697
6727
|
signalId,
|
|
6728
|
+
positionCycleId,
|
|
6698
6729
|
direction,
|
|
6699
6730
|
qty,
|
|
6700
6731
|
timestamp,
|
|
@@ -6705,6 +6736,7 @@ var createTestConnector = (connector, context) => {
|
|
|
6705
6736
|
entrySlippageCost
|
|
6706
6737
|
}) => ({
|
|
6707
6738
|
signalId,
|
|
6739
|
+
positionCycleId,
|
|
6708
6740
|
direction,
|
|
6709
6741
|
qty,
|
|
6710
6742
|
closedQty: 0,
|
|
@@ -7411,6 +7443,7 @@ var createTestConnector = (connector, context) => {
|
|
|
7411
7443
|
currentEntryLegResults.push(
|
|
7412
7444
|
createOpenTradeResult({
|
|
7413
7445
|
signalId: increaseSignalId,
|
|
7446
|
+
positionCycleId: currentSignalId ?? increaseSignalId,
|
|
7414
7447
|
direction: order.direction,
|
|
7415
7448
|
qty: orderQty,
|
|
7416
7449
|
timestamp: order.timestamp,
|
|
@@ -7516,6 +7549,7 @@ var createTestConnector = (connector, context) => {
|
|
|
7516
7549
|
currentPositionProfit = profit;
|
|
7517
7550
|
const openTradeResult = createOpenTradeResult({
|
|
7518
7551
|
signalId: currentSignalId ?? "",
|
|
7552
|
+
positionCycleId: currentSignalId ?? "",
|
|
7519
7553
|
direction: order.direction,
|
|
7520
7554
|
qty: orderQty,
|
|
7521
7555
|
timestamp: order.timestamp,
|