@tradejs/node 1.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.
@@ -0,0 +1,201 @@
1
+ import {
2
+ importTradejsModule,
3
+ loadTradejsConfig,
4
+ resolvePluginModuleSpecifier
5
+ } from "./chunk-DE7ADBIR.mjs";
6
+
7
+ // src/strategy/manifests.ts
8
+ import {
9
+ registerIndicatorEntries,
10
+ resetIndicatorRegistryCache
11
+ } from "@tradejs/core/indicators";
12
+ import { logger } from "@tradejs/infra/logger";
13
+ var strategyCreators = /* @__PURE__ */ new Map();
14
+ var strategyManifestsMap = /* @__PURE__ */ new Map();
15
+ var pluginsLoadPromise = null;
16
+ var toUniqueModules = (modules = []) => [
17
+ ...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
18
+ ];
19
+ var getConfiguredPluginModuleNames = async () => {
20
+ const config = await loadTradejsConfig();
21
+ return {
22
+ strategyModules: toUniqueModules(config.strategies),
23
+ indicatorModules: toUniqueModules(config.indicators)
24
+ };
25
+ };
26
+ var extractModuleEntries = (moduleExport, key) => {
27
+ if (!moduleExport || typeof moduleExport !== "object") {
28
+ return null;
29
+ }
30
+ const candidate = moduleExport;
31
+ if (Array.isArray(candidate[key])) {
32
+ return candidate[key];
33
+ }
34
+ const defaultExport = candidate.default;
35
+ if (defaultExport && Array.isArray(defaultExport[key])) {
36
+ return defaultExport[key];
37
+ }
38
+ return null;
39
+ };
40
+ var extractStrategyPluginDefinition = (moduleExport) => {
41
+ const strategyEntries = extractModuleEntries(
42
+ moduleExport,
43
+ "strategyEntries"
44
+ );
45
+ return strategyEntries ? { strategyEntries } : null;
46
+ };
47
+ var extractIndicatorPluginDefinition = (moduleExport) => {
48
+ const indicatorEntries = extractModuleEntries(
49
+ moduleExport,
50
+ "indicatorEntries"
51
+ );
52
+ return indicatorEntries ? { indicatorEntries } : null;
53
+ };
54
+ var registerEntries = (entries, source) => {
55
+ for (const entry of entries) {
56
+ const strategyName = entry.manifest?.name;
57
+ if (!strategyName) {
58
+ logger.warn("Skip strategy entry without name from %s", source);
59
+ continue;
60
+ }
61
+ if (strategyCreators.has(strategyName)) {
62
+ logger.warn(
63
+ 'Skip duplicate strategy "%s" from %s: already registered',
64
+ strategyName,
65
+ source
66
+ );
67
+ continue;
68
+ }
69
+ strategyCreators.set(strategyName, entry.creator);
70
+ strategyManifestsMap.set(strategyName, entry.manifest);
71
+ }
72
+ };
73
+ var importStrategyPluginModule = async (moduleName) => {
74
+ if (typeof importTradejsModule === "function") {
75
+ return importTradejsModule(moduleName);
76
+ }
77
+ return import(
78
+ /* webpackIgnore: true */
79
+ moduleName
80
+ );
81
+ };
82
+ var ensureStrategyPluginsLoaded = async () => {
83
+ if (pluginsLoadPromise) {
84
+ return pluginsLoadPromise;
85
+ }
86
+ pluginsLoadPromise = (async () => {
87
+ const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames();
88
+ const strategySet = new Set(strategyModules);
89
+ const indicatorSet = new Set(indicatorModules);
90
+ const pluginModuleNames = [
91
+ .../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
92
+ ];
93
+ if (!pluginModuleNames.length) return;
94
+ for (const moduleName of pluginModuleNames) {
95
+ try {
96
+ const resolvedModuleName = resolvePluginModuleSpecifier(moduleName);
97
+ const moduleExport = await importStrategyPluginModule(resolvedModuleName);
98
+ if (strategySet.has(moduleName)) {
99
+ const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
100
+ if (!pluginDefinition) {
101
+ logger.warn(
102
+ 'Skip strategy plugin "%s": export { strategyEntries } is missing',
103
+ moduleName
104
+ );
105
+ } else {
106
+ registerEntries(pluginDefinition.strategyEntries, moduleName);
107
+ }
108
+ }
109
+ if (indicatorSet.has(moduleName)) {
110
+ const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
111
+ if (!indicatorPluginDefinition) {
112
+ logger.warn(
113
+ 'Skip indicator plugin "%s": export { indicatorEntries } is missing',
114
+ moduleName
115
+ );
116
+ } else {
117
+ registerIndicatorEntries(
118
+ indicatorPluginDefinition.indicatorEntries,
119
+ moduleName
120
+ );
121
+ }
122
+ }
123
+ if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
124
+ logger.warn(
125
+ 'Skip plugin "%s": no strategy/indicator sections requested in config',
126
+ moduleName
127
+ );
128
+ }
129
+ } catch (error) {
130
+ logger.warn(
131
+ 'Failed to load plugin "%s": %s',
132
+ moduleName,
133
+ String(error)
134
+ );
135
+ }
136
+ }
137
+ })();
138
+ return pluginsLoadPromise;
139
+ };
140
+ var ensureIndicatorPluginsLoaded = ensureStrategyPluginsLoaded;
141
+ var getStrategyCreator = async (name) => {
142
+ await ensureStrategyPluginsLoaded();
143
+ return strategyCreators.get(name);
144
+ };
145
+ var getAvailableStrategyNames = async () => {
146
+ await ensureStrategyPluginsLoaded();
147
+ return [...strategyCreators.keys()].sort((a, b) => a.localeCompare(b));
148
+ };
149
+ var getRegisteredStrategies = () => {
150
+ return Object.fromEntries(strategyCreators.entries());
151
+ };
152
+ var getRegisteredManifests = () => {
153
+ return [...strategyManifestsMap.values()];
154
+ };
155
+ var getStrategyManifest = (name) => {
156
+ return name ? strategyManifestsMap.get(name) : void 0;
157
+ };
158
+ var isKnownStrategy = (name) => {
159
+ return strategyCreators.has(name);
160
+ };
161
+ var registerStrategyEntries = (entries) => {
162
+ registerEntries(entries, "runtime");
163
+ };
164
+ var resetStrategyRegistryCache = () => {
165
+ strategyCreators.clear();
166
+ strategyManifestsMap.clear();
167
+ resetIndicatorRegistryCache();
168
+ pluginsLoadPromise = null;
169
+ };
170
+ var strategies = new Proxy(
171
+ {},
172
+ {
173
+ get: (_target, property) => {
174
+ if (typeof property !== "string") {
175
+ return void 0;
176
+ }
177
+ return strategyCreators.get(property);
178
+ },
179
+ ownKeys: () => {
180
+ return [...strategyCreators.keys()];
181
+ },
182
+ getOwnPropertyDescriptor: () => ({
183
+ enumerable: true,
184
+ configurable: true
185
+ })
186
+ }
187
+ );
188
+
189
+ export {
190
+ ensureStrategyPluginsLoaded,
191
+ ensureIndicatorPluginsLoaded,
192
+ getStrategyCreator,
193
+ getAvailableStrategyNames,
194
+ getRegisteredStrategies,
195
+ getRegisteredManifests,
196
+ getStrategyManifest,
197
+ isKnownStrategy,
198
+ registerStrategyEntries,
199
+ resetStrategyRegistryCache,
200
+ strategies
201
+ };
@@ -0,0 +1,49 @@
1
+ import {
2
+ getStrategyManifest
3
+ } from "./chunk-MHCXPD2B.mjs";
4
+
5
+ // src/strategyAdapters/ml.ts
6
+ var defaultMlAdapter = {
7
+ normalizeStrategyConfig: (strategyConfig) => strategyConfig
8
+ };
9
+ var getStrategyMlAdapter = (strategy) => {
10
+ const strategyAdapter = getStrategyManifest(strategy)?.mlAdapter;
11
+ if (!strategyAdapter) return defaultMlAdapter;
12
+ return {
13
+ ...defaultMlAdapter,
14
+ ...strategyAdapter
15
+ };
16
+ };
17
+
18
+ // src/mlPayload.ts
19
+ var normalizeStrategyConfig = (strategyConfig, strategyName) => {
20
+ return getStrategyMlAdapter(strategyName).normalizeStrategyConfig?.(
21
+ strategyConfig
22
+ );
23
+ };
24
+ var buildMlPayload = (payload) => {
25
+ const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
26
+ const mlAdapter = getStrategyMlAdapter(strategyName);
27
+ const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
28
+ const nextSignal = {
29
+ ...normalizedSignal,
30
+ indicators: {
31
+ ...normalizedSignal?.indicators ?? {}
32
+ }
33
+ };
34
+ const nextContext = payload.context ? {
35
+ ...payload.context,
36
+ strategyConfig: normalizeStrategyConfig(
37
+ payload.context.strategyConfig,
38
+ strategyName
39
+ )
40
+ } : void 0;
41
+ return {
42
+ signal: nextSignal,
43
+ context: nextContext
44
+ };
45
+ };
46
+
47
+ export {
48
+ buildMlPayload
49
+ };
@@ -0,0 +1,294 @@
1
+ import {
2
+ getStrategyManifest
3
+ } from "./chunk-MHCXPD2B.mjs";
4
+
5
+ // src/ai.ts
6
+ import { setData, redisKeys } from "@tradejs/infra/redis";
7
+
8
+ // src/aiShared.ts
9
+ var MAX_AI_SERIES_POINTS = 5;
10
+ var trimSeriesDeep = (value) => {
11
+ if (Array.isArray(value)) {
12
+ const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
13
+ const isMatrix = trimmed.every((item) => Array.isArray(item));
14
+ if (isMatrix) {
15
+ return trimmed;
16
+ }
17
+ return trimmed.map(
18
+ (item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
19
+ );
20
+ }
21
+ if (value && typeof value === "object") {
22
+ return Object.fromEntries(
23
+ Object.entries(value).map(([key, nested]) => [
24
+ key,
25
+ trimSeriesDeep(nested)
26
+ ])
27
+ );
28
+ }
29
+ return value;
30
+ };
31
+
32
+ // src/strategyAdapters/ai.ts
33
+ var buildBaseAiPayload = (signal) => ({
34
+ signal: {
35
+ symbol: signal.symbol,
36
+ signalId: signal.signalId,
37
+ interval: signal.interval,
38
+ direction: signal.direction,
39
+ timestamp: signal.timestamp,
40
+ strategy: signal.strategy,
41
+ prices: {
42
+ currentPrice: signal.prices.currentPrice,
43
+ takeProfitPrice: signal.prices.takeProfitPrice,
44
+ stopLossPrice: signal.prices.stopLossPrice
45
+ }
46
+ },
47
+ figures: trimSeriesDeep(signal.figures ?? {}),
48
+ indicators: trimSeriesDeep(signal.indicators),
49
+ additionalIndicators: trimSeriesDeep(signal.additionalIndicators ?? {})
50
+ });
51
+ var defaultAiAdapter = {};
52
+ var getStrategyAiAdapter = (strategy) => getStrategyManifest(strategy)?.aiAdapter ?? defaultAiAdapter;
53
+ var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy);
54
+ var buildAiPayloadByStrategy = (signal) => {
55
+ const basePayload = buildBaseAiPayload(signal);
56
+ const adapter = getSignalAiAdapter(signal);
57
+ return adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
58
+ };
59
+ var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
60
+ var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
61
+ signal,
62
+ payload
63
+ }) ?? "";
64
+
65
+ // src/ai.ts
66
+ var parseAIResponse = (input) => {
67
+ try {
68
+ if (typeof input === "object" && input !== null) return input;
69
+ const match = input.match(/\{[\s\S]*\}/);
70
+ if (!match) throw new Error("JSON block not found");
71
+ return JSON.parse(match[0]);
72
+ } catch (err) {
73
+ console.error("\u274C \u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0430\u0440\u0441\u0438\u043D\u0433\u0430 AI-\u043E\u0442\u0432\u0435\u0442\u0430:", err);
74
+ console.log("\u{1F50D} \u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u0442\u0435\u043A\u0441\u0442:", input);
75
+ return {};
76
+ }
77
+ };
78
+ var normalizeResponseContent = (content) => {
79
+ if (typeof content === "string" || content && typeof content === "object") {
80
+ if (typeof content !== "object" || !Array.isArray(content)) {
81
+ return content;
82
+ }
83
+ }
84
+ if (Array.isArray(content)) {
85
+ const text = content.map((part) => typeof part?.text === "string" ? part.text : "").join("\n").trim();
86
+ return text;
87
+ }
88
+ return String(content ?? "");
89
+ };
90
+ var normalizeAnalysis = (raw) => {
91
+ const direction = raw?.direction === "LONG" || raw?.direction === "SHORT" ? raw.direction : null;
92
+ const qualityNum = typeof raw?.quality === "number" ? Math.max(1, Math.min(5, Math.round(raw.quality))) : void 0;
93
+ const toNumberOrNull = (value) => {
94
+ if (typeof value === "number" && Number.isFinite(value)) return value;
95
+ if (typeof value === "string" && value.trim()) {
96
+ const parsed = Number(value);
97
+ if (Number.isFinite(parsed)) return parsed;
98
+ }
99
+ return null;
100
+ };
101
+ const toText = (value) => typeof value === "string" ? value.slice(0, 400) : void 0;
102
+ return {
103
+ direction,
104
+ quality: qualityNum,
105
+ needRetest: Boolean(raw?.needRetest),
106
+ retestPrice: toNumberOrNull(raw?.retestPrice),
107
+ takeProfitPrice: toNumberOrNull(raw?.takeProfitPrice),
108
+ stopLossPrice: toNumberOrNull(raw?.stopLossPrice),
109
+ setup: toText(raw?.setup),
110
+ confirmations: toText(raw?.confirmations),
111
+ btcContext: toText(raw?.btcContext),
112
+ retestPlan: toText(raw?.retestPlan),
113
+ riskLevels: toText(raw?.riskLevels),
114
+ qualityReason: toText(raw?.qualityReason),
115
+ triggerInvalidation: toText(raw?.triggerInvalidation),
116
+ comment: typeof raw?.comment === "string" ? raw.comment.slice(0, 1024) : ""
117
+ };
118
+ };
119
+ var buildAiSystemPrompt = (signal) => `
120
+ \u0422\u044B \u2014 \u043F\u043E\u043C\u043E\u0449\u043D\u0438\u043A \u043A\u0440\u0438\u043F\u0442\u043E-\u0442\u0440\u0435\u0439\u0434\u0435\u0440\u0430.
121
+ \u0410\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u0443\u0439 \u043F\u0440\u0438\u0441\u043B\u0430\u043D\u043D\u044B\u0439 JSON \u0441\u043E \u0441\u0434\u0435\u043B\u043A\u043E\u0439, \u0441\u0432\u0435\u0447\u0430\u043C\u0438, \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u0430\u043C\u0438 (\u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC \u043D\u0430 \u0440\u0430\u0437\u043D\u044B\u0445 \u0422\u0424) \u0438 \u0444\u0438\u0433\u0443\u0440\u0430\u043C\u0438/\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438.
122
+ \u0414\u0430\u043D\u043D\u044B\u0435 \u0440\u044F\u0434\u043E\u0432 \u0443\u0436\u0435 \u0443\u043A\u043E\u0440\u043E\u0447\u0435\u043D\u044B \u0434\u043E \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0445 5 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0439.
123
+
124
+ \u0412\u0410\u0416\u041D\u041E:
125
+ - \u041D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u0434\u0430\u043D\u043D\u044B\u0435.
126
+ - \u041E\u043F\u0438\u0440\u0430\u0439\u0441\u044F \u043D\u0430 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0438 \u0443\u0440\u043E\u0432\u043D\u0438 \u0438\u0441\u0445\u043E\u0434\u043D\u043E\u0433\u043E \u0441\u0438\u0433\u043D\u0430\u043B\u0430, \u043D\u043E \u043C\u043E\u0436\u0435\u0448\u044C \u043D\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0442\u044C\u0441\u044F \u0441 \u043D\u0438\u043C\u0438.
127
+ - \u0423\u0447\u0438\u0442\u044B\u0432\u0430\u0439, \u0447\u0442\u043E \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u0441\u0442\u0440\u043E\u0435\u043D \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0435\u0439, \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u043E\u0439 \u0432 \u043F\u043E\u043B\u0435 signal.strategy.
128
+ - \u0422\u0432\u043E\u044F \u0446\u0435\u043B\u044C: \u0434\u0430\u0442\u044C \u041F\u0420\u0410\u041A\u0422\u0418\u0427\u041D\u042B\u0419 short-term trade plan, \u0430 \u043D\u0435 \u043E\u0431\u0449\u0438\u0435 \u0441\u043B\u043E\u0432\u0430.
129
+ - \u041D\u0435 \u043F\u0438\u0448\u0438 \u0430\u0431\u0441\u0442\u0440\u0430\u043A\u0442\u043D\u043E \u0432\u0440\u043E\u0434\u0435 "\u0435\u0441\u0442\u044C momentum/slope" \u0431\u0435\u0437 \u043F\u0440\u0438\u0432\u044F\u0437\u043A\u0438 \u043A \u0440\u0435\u0448\u0435\u043D\u0438\u044E.
130
+ - \u041F\u0438\u0448\u0438 comment \u043F\u043E-\u0440\u0443\u0441\u0441\u043A\u0438.
131
+ - \u0415\u0441\u043B\u0438 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0441\u0442\u044C \u043D\u0435\u043F\u043E\u043B\u043D\u0430\u044F, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u044B\u0435 \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043A\u0438 ("\u0441\u043A\u043E\u0440\u0435\u0435", "\u043F\u043E\u043A\u0430 \u043D\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F", "\u0432\u0435\u0440\u043E\u044F\u0442\u043D\u043E"), \u0430 \u043D\u0435 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0447\u043D\u044B\u0435 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
132
+
133
+ \u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u0441\u0442\u0440\u043E\u0433\u043E \u041E\u0414\u041D\u0418\u041C JSON-\u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u0431\u0435\u0437 \u0442\u0435\u043A\u0441\u0442\u0430 \u0432\u043E\u043A\u0440\u0443\u0433:
134
+
135
+ {
136
+ "direction": payload.signal.direction | null,
137
+ "quality": 1 | 2 | 3 | 4 | 5,
138
+ "needRetest": boolean,
139
+ "retestPrice": number | null,
140
+ "takeProfitPrice": number | null,
141
+ "stopLossPrice": number | null,
142
+ "setup": string,
143
+ "confirmations": string,
144
+ "btcContext": string,
145
+ "retestPlan": string,
146
+ "riskLevels": string,
147
+ "qualityReason": string,
148
+ "triggerInvalidation": string
149
+ }
150
+
151
+ - \u041D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439 \u0434\u0440\u0443\u0433\u0438\u0435 \u043F\u043E\u043B\u044F.
152
+ - \u0412\u0441\u0435 \u0447\u0438\u0441\u043B\u0430 \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u043A\u043E\u043D\u0435\u0447\u043D\u044B\u043C\u0438 (finite), \u0431\u0435\u0437 NaN/Infinity.
153
+ - \u0412\u0441\u0435 \u0442\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0435 \u043F\u043E\u043B\u044F \u2014 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0435 \u0441\u0442\u0440\u043E\u043A\u0438 (\u0431\u0435\u0437 \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u043E\u0432, \u0431\u0435\u0437 markdown-\u0441\u043F\u0438\u0441\u043A\u043E\u0432).
154
+ - "direction" \u2014 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0441\u0434\u0435\u043B\u043A\u0438: \u043B\u0438\u0431\u043E \u0420\u041E\u0412\u041D\u041E payload.signal.direction, \u043B\u0438\u0431\u043E null (\u0435\u0441\u043B\u0438 \u0441\u0434\u0435\u043B\u043A\u0443 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435 \u043E\u0434\u043E\u0431\u0440\u044F\u0435\u0448\u044C). \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0439 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u043E\u0435 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435.
155
+ - "quality" \u2014 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u043E \u0412\u0425\u041E\u0414\u0410 \u0418\u041C\u0415\u041D\u041D\u041E \u0421\u0415\u0419\u0427\u0410\u0421 (timing + \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F), \u0430 \u043D\u0435 \u043E\u0431\u0449\u0430\u044F \u0438\u0434\u0435\u044F \u0441\u0435\u0442\u0430\u043F\u0430.
156
+ - "needRetest" \u2014 \u043D\u0443\u0436\u043D\u043E \u043B\u0438 \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0440\u0435\u0442\u0435\u0441\u0442\u0430 \u043F\u0435\u0440\u0435\u0434 \u0432\u0445\u043E\u0434\u043E\u043C \u043F\u043E \u0442\u0435\u043A\u0443\u0449\u0435\u043C\u0443 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044E.
157
+ - "retestPrice" \u2014 \u0446\u0435\u043D\u0430/\u0443\u0440\u043E\u0432\u0435\u043D\u044C, \u043F\u043E \u043A\u043E\u0442\u043E\u0440\u043E\u043C\u0443 \u0431\u0443\u0434\u0435\u043C \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0442\u044C \u0440\u0435\u0442\u0435\u0441\u0442 (\u0438\u043B\u0438 null, \u0435\u0441\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D / \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u043D\u0435 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0451\u043D).
158
+ - "takeProfitPrice" \u0438 "stopLossPrice" \u2014 \u0442\u0432\u043E\u0438 \u0443\u0440\u043E\u0432\u043D\u0438 \u0434\u043B\u044F \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u0433\u043E \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F. \u0415\u0441\u043B\u0438 \u0441\u0434\u0435\u043B\u043A\u0438 \u043D\u0435\u0442, \u043E\u0431\u0430 null.
159
+ - \u0420\u0430\u0437\u0431\u0438\u0432\u0430\u0439 \u0430\u043D\u0430\u043B\u0438\u0437 \u043D\u0430 \u043F\u043E\u043B\u044F:
160
+ - "setup" \u2014 \u0447\u0442\u043E \u0437\u0430 \u0441\u0435\u0442\u0430\u043F \u043F\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435/\u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 \u0441\u0435\u0439\u0447\u0430\u0441.
161
+ - "confirmations" \u2014 2-4 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F/\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u043F\u043E \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u0430\u043C \u043C\u043E\u043D\u0435\u0442\u044B.
162
+ - "btcContext" \u2014 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043B\u0438 BTC \u0438\u0434\u0435\u044E \u0438\u043B\u0438 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442.
163
+ - "retestPlan" \u2014 \u0447\u0442\u043E \u0436\u0434\u0430\u0442\u044C \u043F\u043E \u0440\u0435\u0442\u0435\u0441\u0442\u0443 (\u0438\u043B\u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u0440\u0435\u0442\u0435\u0441\u0442 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D).
164
+ - "riskLevels" \u2014 \u043A\u0440\u0430\u0442\u043A\u043E \u043F\u0440\u043E TP/SL \u0438 \u0440\u0438\u0441\u043A-\u043F\u0440\u043E\u0444\u0438\u043B\u044C.
165
+ - "qualityReason" \u2014 \u043F\u043E\u0447\u0435\u043C\u0443 quality \u0438\u043C\u0435\u043D\u043D\u043E \u0442\u0430\u043A\u043E\u0439.
166
+ - "triggerInvalidation" \u2014 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0432\u0445\u043E\u0434 / \u0447\u0442\u043E \u043E\u0442\u043C\u0435\u043D\u044F\u0435\u0442 \u0438\u0434\u0435\u044E.
167
+ - "comment" \u043D\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D. \u0415\u0441\u043B\u0438 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0448\u044C, \u043D\u0435 \u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u044B\u0435 \u043F\u043E\u043B\u044F.
168
+
169
+ \u0415\u0441\u043B\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E \u0438\u043B\u0438 \u0441\u0435\u0442\u0430\u043F \u0441\u043B\u0430\u0431\u044B\u0439 \u2014 \u0432\u0435\u0440\u043D\u0438 "direction": null, quality <= 2 \u0438 \u043E\u0431\u044A\u044F\u0441\u043D\u0438 \u043F\u043E\u0447\u0435\u043C\u0443.
170
+
171
+ \u0421\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0432\u0445\u043E\u0434\u043D\u043E\u0433\u043E payload (JSON \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F):
172
+ - payload.signal:
173
+ symbol, signalId, interval, direction, timestamp, strategy, prices
174
+ - payload.signal.prices:
175
+ currentPrice, takeProfitPrice, stopLossPrice
176
+ - payload.figures:
177
+ \u0441\u043B\u043E\u0432\u0430\u0440\u044C strategy-specific \u0444\u0438\u0433\u0443\u0440/\u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0438 (\u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C). \u041A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u043F\u043E\u043B\u044F \u0437\u0430\u0432\u0438\u0441\u044F\u0442 \u043E\u0442 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438.
178
+ - payload.indicators:
179
+ \u0441\u043B\u043E\u0432\u0430\u0440\u044C \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u043E\u0432/\u0440\u044F\u0434\u043E\u0432 \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC; \u0440\u044F\u0434\u044B \u0443\u0436\u0435 \u043E\u0431\u0440\u0435\u0437\u0430\u043D\u044B \u0434\u043E \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0445 5 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0439.
180
+ \u041F\u0430\u0442\u0442\u0435\u0440\u043D\u044B \u043A\u043B\u044E\u0447\u0435\u0439:
181
+ \u2022 \u043C\u043E\u043D\u0435\u0442\u0430: maFast, atrPct, macd..., candles15m/candles1h/candles4h/candles1d, \u0430 \u0442\u0430\u043A\u0436\u0435 *1h/*4h/*1d
182
+ \u2022 BTC: btcMaFast, btcAtr, btcMacd..., btcCandles*, \u0430 \u0442\u0430\u043A\u0436\u0435 btc*1h/*4h/*1d
183
+ \u2022 \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0435 \u043A\u043B\u044E\u0447\u0438 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438 \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u044B (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 correlation, spread, touches, distance)
184
+
185
+ \u041A\u0430\u043A \u0430\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C (\u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u044B):
186
+ 1) \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u0446\u0435\u043D\u044B \u0438 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044E/\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0441\u0435\u0442\u0430\u043F\u0430 \u0438\u0437 payload.figures. \u042D\u0442\u043E \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u043E\u0432.
187
+ 2) \u0417\u0430\u0442\u0435\u043C \u043E\u0446\u0435\u043D\u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435/\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043F\u043E \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u0430\u043C \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u043C\u043E\u043D\u0435\u0442\u044B.
188
+ 3) \u0417\u0430\u0442\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 BTC (\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0438\u043B\u0438 \u043B\u043E\u043C\u0430\u0435\u0442 \u0438\u0434\u0435\u044E).
189
+ 4) \u0422\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E \u0432\u044B\u0431\u0435\u0440\u0438 direction, quality \u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043F\u043E \u0440\u0435\u0442\u0435\u0441\u0442\u0443.
190
+ 5) \u0415\u0441\u043B\u0438 \u0435\u0441\u0442\u044C \u0441\u0438\u043B\u044C\u043D\u044B\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B, \u0441\u043D\u0438\u0436\u0430\u0439 quality \u0438\u043B\u0438 \u0441\u0442\u0430\u0432\u044C null.
191
+
192
+ \u042F\u0432\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u043F\u0440\u0438 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u0432:
193
+ - \u0415\u0441\u043B\u0438 \u0444\u0438\u0433\u0443\u0440\u0430/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0446\u0435\u043D\u044B \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B \u0438\u043B\u0438 \u0441\u043E\u043C\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B, \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u044B \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u044B "\u0441\u043F\u0430\u0441\u0430\u0442\u044C" \u0441\u0435\u0442\u0430\u043F.
194
+ - \u0415\u0441\u043B\u0438 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u043E\u043A, \u043D\u043E BTC \u0438/\u0438\u043B\u0438 \u043A\u043B\u044E\u0447\u0435\u0432\u044B\u0435 \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u044B \u0437\u0430\u043C\u0435\u0442\u043D\u043E \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0442, \u043E\u0431\u044B\u0447\u043D\u043E quality <= 3.
195
+ - \u0415\u0441\u043B\u0438 \u043D\u0435 \u043E\u0434\u043E\u0431\u0440\u044F\u0435\u0448\u044C \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0434\u0435\u043B\u043A\u0443 (direction=null), \u0432 comment \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043A\u0440\u0430\u0442\u043A\u043E \u043D\u0430\u0437\u043E\u0432\u0438 \u0433\u043B\u0430\u0432\u043D\u0443\u044E \u043F\u0440\u0438\u0447\u0438\u043D\u0443.
196
+ \u0415\u0441\u043B\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0448\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u044B\u0435 \u043F\u043E\u043B\u044F, \u0443\u043A\u0430\u0436\u0438 \u0433\u043B\u0430\u0432\u043D\u0443\u044E \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0432 "qualityReason" \u0438/\u0438\u043B\u0438 "triggerInvalidation".
197
+
198
+ \u041F\u0440\u0430\u0432\u0438\u043B\u0430 \u0434\u043B\u044F direction / TP / SL:
199
+ - direction = LONG \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u043E\u0435 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u0435 \u0432\u0432\u0435\u0440\u0445 \u043E\u0431\u043E\u0441\u043D\u043E\u0432\u0430\u043D\u043E; SHORT \u2014 \u0432\u043D\u0438\u0437; \u0438\u043D\u0430\u0447\u0435 null.
200
+ - \u0414\u043B\u044F LONG \u043E\u0431\u044B\u0447\u043D\u043E stopLossPrice < currentPrice < takeProfitPrice.
201
+ - \u0414\u043B\u044F SHORT \u043E\u0431\u044B\u0447\u043D\u043E takeProfitPrice < currentPrice < stopLossPrice.
202
+ - TP/SL \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u0440\u0435\u0430\u043B\u0438\u0441\u0442\u0438\u0447\u043D\u044B\u043C\u0438 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u044B (\u043D\u0435 \u0441\u0442\u0430\u0432\u044C absurdly far/near \u0443\u0440\u043E\u0432\u043D\u0438).
203
+ - \u0422\u0440\u0435\u0431\u0443\u0435\u043C\u043E\u0435 \u0441\u043E\u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u0435 reward:risk \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0445\u0443\u0436\u0435 1:3 (\u0442\u043E \u0435\u0441\u0442\u044C reward/risk >= 0.33).
204
+ - \u0415\u0441\u043B\u0438 direction = null, \u0442\u043E takeProfitPrice = null \u0438 stopLossPrice = null.
205
+ - \u0415\u0441\u043B\u0438 needRetest = false, \u0442\u043E retestPrice = null.
206
+ - \u0415\u0441\u043B\u0438 needRetest = true, retestPrice \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0443\u043A\u0430\u0437\u0430\u043D (finite number) \u0438 \u0441\u0432\u044F\u0437\u0430\u043D \u0441 \u0443\u0440\u043E\u0432\u043D\u0435\u043C \u0440\u0435\u0442\u0435\u0441\u0442\u0430/\u043F\u0440\u043E\u0431\u043E\u044F.
207
+ - Sanity-check \u043F\u0435\u0440\u0435\u0434 \u043E\u0442\u0432\u0435\u0442\u043E\u043C: \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u043E\u0441\u0442\u044C direction \u0441 TP/SL \u0438 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u043E\u0439.
208
+
209
+ \u0428\u043A\u0430\u043B\u0430 quality (\u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0432\u0441\u044E \u0448\u043A\u0430\u043B\u0443, \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439):
210
+ - 1: \u043F\u043B\u043E\u0445\u043E\u0439/\u0445\u0430\u043E\u0442\u0438\u0447\u043D\u044B\u0439 \u0441\u0435\u0442\u0430\u043F, \u0441\u0438\u043B\u044C\u043D\u044B\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B, \u0432\u0445\u043E\u0434 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D
211
+ - 2: \u0441\u043B\u0430\u0431\u044B\u0439 \u0441\u0435\u0442\u0430\u043F, \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439 \u043C\u0430\u043B\u043E, \u043B\u0443\u0447\u0448\u0435 \u043F\u0440\u043E\u043F\u0443\u0441\u043A
212
+ - 3: \u0441\u0440\u0435\u0434\u043D\u0438\u0439 \u0441\u0435\u0442\u0430\u043F, \u0435\u0441\u0442\u044C \u0438\u0434\u0435\u044F, \u043D\u043E \u0435\u0441\u0442\u044C \u0437\u0430\u043C\u0435\u0442\u043D\u044B\u0435 \u0440\u0438\u0441\u043A\u0438/\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B
213
+ - 4: \u0445\u043E\u0440\u043E\u0448\u0438\u0439 \u0441\u0435\u0442\u0430\u043F, \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439, \u0440\u0438\u0441\u043A\u0438 \u043F\u043E\u043D\u044F\u0442\u043D\u044B
214
+ - 5: \u043E\u0447\u0435\u043D\u044C \u0441\u0438\u043B\u044C\u043D\u044B\u0439 \u0441\u0435\u0442\u0430\u043F, \u0447\u0438\u0441\u0442\u0430\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 + \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F + \u0430\u0434\u0435\u043A\u0432\u0430\u0442\u043D\u044B\u0439 \u0440\u0438\u0441\u043A
215
+
216
+ \u0422\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043A \u043F\u043E\u043B\u0435\u0437\u043D\u043E\u043C\u0443 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u043C\u0443 \u0430\u043D\u0430\u043B\u0438\u0437\u0443 (\u0431\u0435\u0437 \u0432\u043E\u0434\u044B):
217
+ - \u0423\u043A\u0430\u0436\u0438 2-4 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0445 \u0444\u0430\u043A\u0442\u043E\u0440\u0430 "\u0437\u0430" \u0438\u043B\u0438 "\u043F\u0440\u043E\u0442\u0438\u0432" \u0441\u0434\u0435\u043B\u043A\u0443 \u0432 "confirmations".
218
+ - \u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0443\u043F\u043E\u043C\u044F\u043D\u0438 \u0440\u043E\u043B\u044C \u043A\u043B\u044E\u0447\u0435\u0432\u043E\u0439 \u0444\u0438\u0433\u0443\u0440\u044B/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0441\u0435\u0442\u0430\u043F\u0430 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \u043F\u0440\u043E\u0431\u043E\u0439/\u0440\u0435\u0442\u0435\u0441\u0442/\u043B\u043E\u0436\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439/\u043A\u0430\u0441\u0430\u043D\u0438\u0435/\u043D\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F).
219
+ - \u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0443\u043F\u043E\u043C\u044F\u043D\u0438 BTC-\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 (\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442, \u043D\u0435\u0439\u0442\u0440\u0430\u043B\u0435\u043D \u0438\u043B\u0438 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442).
220
+ - \u041E\u0431\u044A\u044F\u0441\u043D\u0438, \u043F\u043E\u0447\u0435\u043C\u0443 quality \u0438\u043C\u0435\u043D\u043D\u043E \u0442\u0430\u043A\u043E\u0439.
221
+ - \u0415\u0441\u043B\u0438 \u043D\u0435 \u0432\u0445\u043E\u0434\u0438\u0448\u044C (direction=null), \u043F\u0440\u044F\u043C\u043E \u0443\u043A\u0430\u0436\u0438 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u043D\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C\u0441\u044F \u0434\u043B\u044F \u0432\u0445\u043E\u0434\u0430.
222
+ - \u0412 "retestPlan" \u043D\u0435 \u043F\u0438\u0448\u0438 \u0442\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0448\u0430\u0431\u043B\u043E\u043D \u0432\u0440\u043E\u0434\u0435 "needRetest=false @ null"; \u043F\u0438\u0448\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u0447\u0435\u0441\u043A\u043E\u0435 \u043E\u0431\u044A\u044F\u0441\u043D\u0435\u043D\u0438\u0435.
223
+ - \u041D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0439 \u043F\u0440\u043E\u0441\u0442\u043E \u043F\u043E\u043B\u044F JSON; \u0434\u0430\u0439 \u0441\u043C\u044B\u0441\u043B \u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u0435.
224
+
225
+ \u041F\u0440\u0430\u0432\u0438\u043B\u0430 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u044F \u043E\u0431\u0440\u0435\u0437\u0430\u043D\u043D\u044B\u0445 \u0440\u044F\u0434\u043E\u0432 (last 5 values):
226
+ - \u041D\u0435 \u0434\u0435\u043B\u0430\u0439 \u0441\u0438\u043B\u044C\u043D\u044B\u0445 \u0432\u044B\u0432\u043E\u0434\u043E\u0432 \u043E \u0434\u043E\u043B\u0433\u043E\u0441\u0440\u043E\u0447\u043D\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E 5 \u0442\u043E\u0447\u043A\u0430\u043C.
227
+ - \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 4h/1d \u0440\u044F\u0434\u044B \u043A\u0430\u043A \u043A\u0440\u0430\u0442\u043A\u0438\u0439 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442, \u0430 \u043D\u0435 \u043A\u0430\u043A \u043F\u043E\u043B\u043D\u0443\u044E \u0438\u0441\u0442\u043E\u0440\u0438\u044E.
228
+ - \u0415\u0441\u043B\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \u043C\u0430\u043B\u043E \u0434\u043B\u044F \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0433\u043E \u0432\u044B\u0432\u043E\u0434\u0430, \u0441\u043D\u0438\u0436\u0430\u0439 quality \u0438 \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u0443\u0439 \u0432\u044B\u0432\u043E\u0434 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u043E.
229
+
230
+ \u041A\u043E\u0440\u043E\u0442\u043A\u0438\u0435 \u043F\u0440\u0438\u043C\u0435\u0440\u044B (few-shot, \u0444\u043E\u0440\u043C\u0430\u0442 \u043E\u0442\u0432\u0435\u0442\u0430):
231
+ {"direction":"LONG","quality":4,"needRetest":true,"retestPrice":100.2,"takeProfitPrice":101.5,"stopLossPrice":98.9,"setup":"\u0412\u0435\u0440\u043E\u044F\u0442\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u0432\u0432\u0435\u0440\u0445, \u043D\u043E \u0432\u0445\u043E\u0434 \u0441\u0435\u0439\u0447\u0430\u0441 \u043B\u0443\u0447\u0448\u0435 \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0443\u0440\u043E\u0432\u043D\u044F.","confirmations":"\u041F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0435\u0441\u0442\u044C \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438\u043C\u043F\u0443\u043B\u044C\u0441\u0430 \u0431\u0435\u0437 \u044F\u0432\u043D\u043E\u0433\u043E \u043F\u0435\u0440\u0435\u0433\u0440\u0435\u0432\u0430, \u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0435\u0449\u0435 \u043D\u0435 \u0438\u0434\u0435\u0430\u043B\u044C\u043D\u043E\u0435.","btcContext":"BTC \u043D\u0435\u0439\u0442\u0440\u0430\u043B\u044C\u043D\u043E \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0438\u0434\u0435\u044E \u0438 \u043D\u0435 \u043C\u0435\u0448\u0430\u0435\u0442 LONG.","retestPlan":"\u0416\u0434\u0430\u0442\u044C \u0440\u0435\u0442\u0435\u0441\u0442 \u0437\u043E\u043D\u044B 100.2 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0432\u044B\u0448\u0435 \u043D\u0435\u0435 \u043F\u0435\u0440\u0435\u0434 \u0432\u0445\u043E\u0434\u043E\u043C.","riskLevels":"TP/SL \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u0435 \u0441\u0442\u043E\u0440\u043E\u043D\u044B \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u044B, \u0440\u0438\u0441\u043A \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0438\u0440\u0443\u0435\u043C\u044B\u0439.","qualityReason":"Quality=4: \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0445\u043E\u0440\u043E\u0448\u0430\u044F, \u043D\u043E \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u0442\u0435\u0441\u0442 \u0434\u043B\u044F \u0431\u043E\u043B\u0435\u0435 \u0447\u0438\u0441\u0442\u043E\u0433\u043E timing.","triggerInvalidation":"\u0412\u0445\u043E\u0434 \u043F\u043E\u0441\u043B\u0435 \u0440\u0435\u0442\u0435\u0441\u0442\u0430 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u044F \u0443\u0440\u043E\u0432\u043D\u044F; \u043E\u0442\u043C\u0435\u043D\u0430 \u043F\u0440\u0438 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0435 \u043F\u043E\u0434 \u043B\u0438\u043D\u0438\u044E."}
232
+ {"direction":null,"quality":2,"needRetest":false,"retestPrice":null,"takeProfitPrice":null,"stopLossPrice":null,"setup":"\u041A\u0430\u0441\u0430\u043D\u0438\u0435/\u0448\u0443\u043C \u0443 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u0431\u0435\u0437 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F.","confirmations":"\u0418\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u044B \u0441\u043C\u0435\u0448\u0430\u043D\u043D\u044B\u0435 \u0438 \u043D\u0435 \u0434\u0430\u044E\u0442 \u0441\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043F\u0440\u0435\u0438\u043C\u0443\u0449\u0435\u0441\u0442\u0432\u0430.","btcContext":"BTC \u0441\u043A\u043E\u0440\u0435\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0438\u043B\u0438 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0438\u0434\u0435\u044E.","retestPlan":"\u0420\u0435\u0442\u0435\u0441\u0442 \u043F\u043E\u043A\u0430 \u043D\u0435 \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0435\u043C, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u043D\u0443\u0436\u0435\u043D \u0441\u0430\u043C \u0444\u0430\u043A\u0442 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F.","riskLevels":"\u0421\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u043E\u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u0432\u0445\u043E\u0434\u0430.","qualityReason":"Quality=2: timing \u0441\u043B\u0430\u0431\u044B\u0439 \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439 \u043C\u0430\u043B\u043E.","triggerInvalidation":"\u0416\u0434\u0430\u0442\u044C \u044F\u0432\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC."}
233
+
234
+ \u0412\u0435\u0440\u043D\u0438 \u0442\u043E\u043B\u044C\u043A\u043E JSON-\u043E\u0431\u044A\u0435\u043A\u0442, \u0431\u0435\u0437 \u043B\u0438\u0448\u043D\u0438\u0445 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432.
235
+ ${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
236
+ `;
237
+ var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
238
+ var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
239
+ \u041F\u0440\u043E\u0430\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u0443\u0439 \u0441\u0434\u0435\u043B\u043A\u0443 \u043F\u043E ${signal.symbol}. \u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u0438\u043C\u0435\u0435\u0442 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 ${signal.direction}.
240
+ \u041E\u0446\u0435\u043D\u0438 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0434\u0435\u043B\u043A\u0443 (\u0431\u0435\u0437 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u043E\u0433\u043E \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F): \u043E\u0434\u043E\u0431\u0440\u044F\u0435\u0448\u044C \u043B\u0438 \u0432\u0445\u043E\u0434 \u0441\u0435\u0439\u0447\u0430\u0441, \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u043E \u0432\u0445\u043E\u0434\u0430 \u0441\u0435\u0439\u0447\u0430\u0441, \u043D\u0443\u0436\u0435\u043D \u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442 \u0438 \u043F\u043E \u043A\u0430\u043A\u043E\u0439 \u0446\u0435\u043D\u0435 \u0435\u0433\u043E \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0442\u044C, \u0430 \u0442\u0430\u043A\u0436\u0435 \u0434\u0430\u0439 TP/SL \u0438 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u0441\u0442\u0440\u043E\u0433\u043E \u0432 \u0437\u0430\u0434\u0430\u043D\u043D\u043E\u043C JSON-\u0444\u043E\u0440\u043C\u0430\u0442\u0435.
241
+
242
+ \u0414\u0430\u043D\u043D\u044B\u0435 \u0441\u0434\u0435\u043B\u043A\u0438:
243
+ ${JSON.stringify(payload)}
244
+ ${buildAiHumanPromptAddonByStrategy(signal, payload)}
245
+ `;
246
+ var askAI = async (signal) => {
247
+ const [{ ChatOpenAI }, { HumanMessage, SystemMessage }] = await Promise.all([
248
+ import("@langchain/openai"),
249
+ import("@langchain/core/messages")
250
+ ]);
251
+ const { symbol } = signal;
252
+ const messages = [];
253
+ const model = new ChatOpenAI({
254
+ temperature: 0.2,
255
+ //modelName: 'anthropic/claude-sonnet-4.5',
256
+ modelName: "google/gemini-3.1-pro-preview",
257
+ openAIApiKey: process.env.OPENAI_API_KEY,
258
+ configuration: {
259
+ baseURL: process.env.OPENAI_API_ENDPOINT || "https://api.openai.com/v1",
260
+ defaultHeaders: {
261
+ "HTTP-Referer": "https://tradejs.dev",
262
+ "X-Title": "Inv"
263
+ }
264
+ }
265
+ });
266
+ messages.push(new SystemMessage(buildAiSystemPrompt(signal)));
267
+ const payload = buildAiPayload(signal);
268
+ messages.push(
269
+ new HumanMessage({
270
+ content: [
271
+ {
272
+ type: "text",
273
+ text: buildAiHumanPrompt(signal, payload)
274
+ }
275
+ ]
276
+ })
277
+ );
278
+ const response = await model.invoke(messages);
279
+ const parsed = parseAIResponse(
280
+ normalizeResponseContent(response.content)
281
+ );
282
+ const content = normalizeAnalysis(parsed);
283
+ await setData(redisKeys.analysis(symbol, signal.signalId), content);
284
+ return content;
285
+ };
286
+
287
+ export {
288
+ MAX_AI_SERIES_POINTS,
289
+ trimSeriesDeep,
290
+ buildAiSystemPrompt,
291
+ buildAiPayload,
292
+ buildAiHumanPrompt,
293
+ askAI
294
+ };
package/dist/cli.d.mts ADDED
@@ -0,0 +1,12 @@
1
+ import { TestStat, TestThresholdsKey, Connector, Signal, Interval } from '@tradejs/types';
2
+
3
+ declare const cleanFiles: (dir: string) => Promise<void>;
4
+ declare const cleanRedis: (area: string) => Promise<void>;
5
+ declare const update: (connector: Connector, interval: Interval, tickers: string[]) => Promise<void>;
6
+ declare const drawStatInCLI: (stat: Partial<TestStat> | undefined, keys: TestThresholdsKey[]) => string[];
7
+ declare const getTickers: (connector: Connector, include?: string, exclude?: string, limit?: number, chunk?: string) => Promise<string[]>;
8
+ declare const makeScreenshots: (signals: Signal[], interval: Interval) => Promise<void>;
9
+ declare const sendToAI: (signals: Signal[]) => Promise<void>;
10
+ declare const sendToTG: (signals: Signal[], imgInterval: Interval) => Promise<void>;
11
+
12
+ export { cleanFiles, cleanRedis, drawStatInCLI, getTickers, makeScreenshots, sendToAI, sendToTG, update };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { TestStat, TestThresholdsKey, Connector, Signal, Interval } from '@tradejs/types';
2
+
3
+ declare const cleanFiles: (dir: string) => Promise<void>;
4
+ declare const cleanRedis: (area: string) => Promise<void>;
5
+ declare const update: (connector: Connector, interval: Interval, tickers: string[]) => Promise<void>;
6
+ declare const drawStatInCLI: (stat: Partial<TestStat> | undefined, keys: TestThresholdsKey[]) => string[];
7
+ declare const getTickers: (connector: Connector, include?: string, exclude?: string, limit?: number, chunk?: string) => Promise<string[]>;
8
+ declare const makeScreenshots: (signals: Signal[], interval: Interval) => Promise<void>;
9
+ declare const sendToAI: (signals: Signal[]) => Promise<void>;
10
+ declare const sendToTG: (signals: Signal[], imgInterval: Interval) => Promise<void>;
11
+
12
+ export { cleanFiles, cleanRedis, drawStatInCLI, getTickers, makeScreenshots, sendToAI, sendToTG, update };