@tradejs/strategies 1.0.4 → 1.0.6
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/README.md +1 -1
- package/dist/chunk-GNQJ5TVU.mjs +687 -0
- package/dist/chunk-H2TU2YMA.mjs +762 -0
- package/dist/chunk-MOBKL73M.mjs +405 -0
- package/dist/chunk-XMRB45ZO.mjs +789 -0
- package/dist/index.d.mts +43 -3
- package/dist/index.d.ts +43 -3
- package/dist/index.js +4043 -468
- package/dist/index.mjs +21 -7
- package/dist/strategy-6TS2NFSC.mjs +736 -0
- package/dist/strategy-AFIGEHDS.mjs +418 -0
- package/dist/{strategy-ZVNTA6UR.mjs → strategy-FYNNJDOH.mjs} +1 -13
- package/dist/strategy-LC2FSFVN.mjs +470 -0
- package/dist/{strategy-UHRWSGZC.mjs → strategy-OI4WRB3S.mjs} +51 -41
- package/package.json +14 -6
- package/dist/chunk-MVIV5ZII.mjs +0 -137
- package/dist/chunk-RYEPHOGL.mjs +0 -28
- package/dist/chunk-ULLCAH5C.mjs +0 -67
- package/dist/strategy-M3BRWDRR.mjs +0 -377
- package/dist/strategy-UZBWST3G.mjs +0 -156
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
// src/TrendLine/adapters/ai.ts
|
|
2
|
+
import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
3
|
+
|
|
4
|
+
// src/TrendLine/guardrails.ts
|
|
5
|
+
var TRENDLINE_CLEAR_BREAK_PCT = 0.35;
|
|
6
|
+
var TRENDLINE_TIMING_WINDOW = 6;
|
|
7
|
+
var WEAK_CLEAN_BREAK_ATR_RATIO_MAX = 0.45;
|
|
8
|
+
var COMPRESSED_CLEAN_BREAK_ATR_RATIO_MAX = 0.6;
|
|
9
|
+
var COMPRESSED_CLEAN_BREAK_DISTANCE_MAX = 120;
|
|
10
|
+
var COMPRESSED_CLEAN_BREAK_TOUCHES_MIN = 5;
|
|
11
|
+
var WEAK_LONG_FAR_BREAK_ATR_RATIO_MAX = 0.6;
|
|
12
|
+
var WEAK_LONG_FAR_BREAK_DISTANCE_MIN = 1e3;
|
|
13
|
+
var WEAK_LONG_FAR_BREAK_BTC_SPREAD_MAX = 0.35;
|
|
14
|
+
var toFiniteNumberOrNull = (value) => {
|
|
15
|
+
const num = Number(value);
|
|
16
|
+
return Number.isFinite(num) ? num : null;
|
|
17
|
+
};
|
|
18
|
+
var getLastFiniteNumber = (value) => {
|
|
19
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return toFiniteNumberOrNull(value[value.length - 1]);
|
|
23
|
+
};
|
|
24
|
+
var getFiniteTailNumbers = (value, count) => {
|
|
25
|
+
if (!Array.isArray(value) || count <= 0) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const tail = value.slice(-count);
|
|
29
|
+
return tail.map((item) => toFiniteNumberOrNull(item));
|
|
30
|
+
};
|
|
31
|
+
var getBias = (fast, slow) => {
|
|
32
|
+
if (fast == null || slow == null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (fast > slow) {
|
|
36
|
+
return "bullish";
|
|
37
|
+
}
|
|
38
|
+
if (fast < slow) {
|
|
39
|
+
return "bearish";
|
|
40
|
+
}
|
|
41
|
+
return "flat";
|
|
42
|
+
};
|
|
43
|
+
var getSpreadPct = (fast, slow) => {
|
|
44
|
+
if (fast == null || slow == null || slow === 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return (fast - slow) / slow * 100;
|
|
48
|
+
};
|
|
49
|
+
var getTrendLineFromPayload = (signal) => signal.figures?.trendLine ?? signal.additionalIndicators?.trendLine ?? null;
|
|
50
|
+
var getSortedTrendLinePoints = (trendLine) => {
|
|
51
|
+
const rawPoints = Array.isArray(trendLine?.points) ? trendLine.points : [];
|
|
52
|
+
return rawPoints.map((point) => {
|
|
53
|
+
if (!point || typeof point !== "object") {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const typedPoint = point;
|
|
57
|
+
const timestamp = toFiniteNumberOrNull(typedPoint.timestamp);
|
|
58
|
+
const value = toFiniteNumberOrNull(typedPoint.value);
|
|
59
|
+
if (timestamp == null || value == null) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return { timestamp, value };
|
|
63
|
+
}).filter(Boolean).sort((left, right) => left.timestamp - right.timestamp);
|
|
64
|
+
};
|
|
65
|
+
var buildTrendLineEvaluator = (trendLine) => {
|
|
66
|
+
const points = getSortedTrendLinePoints(trendLine);
|
|
67
|
+
if (points.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const firstPoint = points[0];
|
|
71
|
+
const lastPoint = points[points.length - 1];
|
|
72
|
+
const deltaTime = lastPoint.timestamp - firstPoint.timestamp;
|
|
73
|
+
if (deltaTime === 0) {
|
|
74
|
+
return {
|
|
75
|
+
firstPoint,
|
|
76
|
+
lastPoint,
|
|
77
|
+
evaluate: (_timestamp) => lastPoint.value
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const slope = (lastPoint.value - firstPoint.value) / deltaTime;
|
|
81
|
+
return {
|
|
82
|
+
firstPoint,
|
|
83
|
+
lastPoint,
|
|
84
|
+
evaluate: (timestamp) => firstPoint.value + slope * (timestamp - firstPoint.timestamp)
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
var getBreakoutSide = ({
|
|
88
|
+
direction,
|
|
89
|
+
priceVsLinePct
|
|
90
|
+
}) => {
|
|
91
|
+
if (direction == null || priceVsLinePct == null) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return direction === "SHORT" ? priceVsLinePct < 0 : priceVsLinePct > 0;
|
|
95
|
+
};
|
|
96
|
+
var getClearBreakAtPct = ({
|
|
97
|
+
direction,
|
|
98
|
+
priceVsLinePct
|
|
99
|
+
}) => {
|
|
100
|
+
if (direction == null || priceVsLinePct == null) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return direction === "SHORT" ? priceVsLinePct <= -TRENDLINE_CLEAR_BREAK_PCT : priceVsLinePct >= TRENDLINE_CLEAR_BREAK_PCT;
|
|
104
|
+
};
|
|
105
|
+
var getLineSlopeDirection = (value) => {
|
|
106
|
+
if (value == null) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (value > 0) {
|
|
110
|
+
return "rising";
|
|
111
|
+
}
|
|
112
|
+
if (value < 0) {
|
|
113
|
+
return "falling";
|
|
114
|
+
}
|
|
115
|
+
return "flat";
|
|
116
|
+
};
|
|
117
|
+
var buildTrendlineStructuralContext = (signal) => {
|
|
118
|
+
const trendLine = getTrendLineFromPayload(signal);
|
|
119
|
+
const currentPrice = toFiniteNumberOrNull(signal.prices?.currentPrice);
|
|
120
|
+
const signalDirection = signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
|
|
121
|
+
const points = Array.isArray(trendLine?.points) ? trendLine.points : [];
|
|
122
|
+
const latestPoint = points.length ? points[points.length - 1] : null;
|
|
123
|
+
const currentLinePrice = toFiniteNumberOrNull(
|
|
124
|
+
latestPoint && typeof latestPoint === "object" ? latestPoint.value : null
|
|
125
|
+
);
|
|
126
|
+
const priceVsLinePct = currentPrice != null && currentLinePrice != null && currentLinePrice !== 0 ? (currentPrice - currentLinePrice) / currentLinePrice * 100 : null;
|
|
127
|
+
const priceVsLineSide = priceVsLinePct == null ? null : priceVsLinePct > 0 ? "above" : priceVsLinePct < 0 ? "below" : "at";
|
|
128
|
+
const priceVsLinePctAbs = priceVsLinePct == null ? null : Math.abs(priceVsLinePct);
|
|
129
|
+
const touchesTotal = toFiniteNumberOrNull(
|
|
130
|
+
signal.additionalIndicators?.touches
|
|
131
|
+
);
|
|
132
|
+
const distance = toFiniteNumberOrNull(signal.additionalIndicators?.distance);
|
|
133
|
+
const touches = touchesTotal != null ? touchesTotal : Array.isArray(trendLine?.touches) ? trendLine.touches.length : null;
|
|
134
|
+
const atrPct = getLastFiniteNumber(signal.indicators?.atrPct);
|
|
135
|
+
const btcMaFast = getLastFiniteNumber(signal.indicators?.btcMaFast);
|
|
136
|
+
const btcMaSlow = getLastFiniteNumber(signal.indicators?.btcMaSlow);
|
|
137
|
+
const btcMaBias = getBias(btcMaFast, btcMaSlow);
|
|
138
|
+
const btcMaSpreadPct = getSpreadPct(btcMaFast, btcMaSlow);
|
|
139
|
+
const btcBiasAligned = signalDirection == null || btcMaBias == null ? null : signalDirection === "SHORT" ? btcMaBias === "bearish" : btcMaBias === "bullish";
|
|
140
|
+
const clearBreak = getClearBreakAtPct({
|
|
141
|
+
direction: signalDirection,
|
|
142
|
+
priceVsLinePct
|
|
143
|
+
});
|
|
144
|
+
const nearLineNoise = priceVsLinePctAbs == null ? null : priceVsLinePctAbs < TRENDLINE_CLEAR_BREAK_PCT;
|
|
145
|
+
const breakVsAtrRatio = priceVsLinePctAbs != null && atrPct != null && atrPct > 0 ? priceVsLinePctAbs / atrPct : null;
|
|
146
|
+
const weakCleanBreak = clearBreak === true && nearLineNoise === false && breakVsAtrRatio != null && breakVsAtrRatio < WEAK_CLEAN_BREAK_ATR_RATIO_MAX;
|
|
147
|
+
const compressedCleanBreak = clearBreak === true && nearLineNoise === false && breakVsAtrRatio != null && breakVsAtrRatio < COMPRESSED_CLEAN_BREAK_ATR_RATIO_MAX && (touches ?? 0) >= COMPRESSED_CLEAN_BREAK_TOUCHES_MIN && distance != null && distance < COMPRESSED_CLEAN_BREAK_DISTANCE_MAX;
|
|
148
|
+
const weakLongFarBreak = signalDirection === "LONG" && trendLine?.mode === "highs" && clearBreak === true && nearLineNoise === false && breakVsAtrRatio != null && breakVsAtrRatio < WEAK_LONG_FAR_BREAK_ATR_RATIO_MAX && distance != null && distance > WEAK_LONG_FAR_BREAK_DISTANCE_MIN && btcBiasAligned === true && btcMaSpreadPct != null && btcMaSpreadPct < WEAK_LONG_FAR_BREAK_BTC_SPREAD_MAX;
|
|
149
|
+
const structuralHardBlockReasons = [];
|
|
150
|
+
if (clearBreak === false) {
|
|
151
|
+
structuralHardBlockReasons.push("no_clear_break");
|
|
152
|
+
}
|
|
153
|
+
if (nearLineNoise === true) {
|
|
154
|
+
structuralHardBlockReasons.push("near_line_noise");
|
|
155
|
+
}
|
|
156
|
+
if (weakCleanBreak) {
|
|
157
|
+
structuralHardBlockReasons.push("weak_clean_break");
|
|
158
|
+
}
|
|
159
|
+
if (compressedCleanBreak) {
|
|
160
|
+
structuralHardBlockReasons.push("compressed_clean_break");
|
|
161
|
+
}
|
|
162
|
+
if (weakLongFarBreak) {
|
|
163
|
+
structuralHardBlockReasons.push("weak_long_far_break");
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
signalDirection,
|
|
167
|
+
mode: typeof trendLine?.mode === "string" ? trendLine.mode : null,
|
|
168
|
+
touches,
|
|
169
|
+
distance,
|
|
170
|
+
currentLinePrice,
|
|
171
|
+
currentPrice,
|
|
172
|
+
priceVsLinePct,
|
|
173
|
+
priceVsLineSide,
|
|
174
|
+
priceVsLinePctAbs,
|
|
175
|
+
clearBreak,
|
|
176
|
+
nearLineNoise,
|
|
177
|
+
atrPct,
|
|
178
|
+
breakVsAtrRatio,
|
|
179
|
+
btcMaFast,
|
|
180
|
+
btcMaSlow,
|
|
181
|
+
btcMaBias,
|
|
182
|
+
btcMaSpreadPct,
|
|
183
|
+
btcBiasAligned,
|
|
184
|
+
weakCleanBreak,
|
|
185
|
+
compressedCleanBreak,
|
|
186
|
+
weakLongFarBreak,
|
|
187
|
+
structuralHardBlockReasons
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
var buildTrendlineTimingContext = ({
|
|
191
|
+
signal,
|
|
192
|
+
candles,
|
|
193
|
+
structuralContext
|
|
194
|
+
}) => {
|
|
195
|
+
const structural = structuralContext ?? buildTrendlineStructuralContext(signal);
|
|
196
|
+
const trendLine = getTrendLineFromPayload(signal);
|
|
197
|
+
const evaluator = buildTrendLineEvaluator(trendLine);
|
|
198
|
+
const timingCandles = Array.isArray(candles) ? candles.slice(-TRENDLINE_TIMING_WINDOW) : [];
|
|
199
|
+
const sortedCandles = [...timingCandles].sort(
|
|
200
|
+
(left, right) => Number(left?.timestamp ?? 0) - Number(right?.timestamp ?? 0)
|
|
201
|
+
);
|
|
202
|
+
const atrTail = getFiniteTailNumbers(
|
|
203
|
+
signal.indicators?.atrPct,
|
|
204
|
+
sortedCandles.length
|
|
205
|
+
);
|
|
206
|
+
const atrValues = Array.from({ length: sortedCandles.length }, (_, index) => {
|
|
207
|
+
const offset = index - (sortedCandles.length - atrTail.length);
|
|
208
|
+
return offset >= 0 ? atrTail[offset] : null;
|
|
209
|
+
});
|
|
210
|
+
const recentSamples = evaluator ? sortedCandles.map((candle, index) => {
|
|
211
|
+
const timestamp = toFiniteNumberOrNull(candle.timestamp);
|
|
212
|
+
const close = toFiniteNumberOrNull(candle.close);
|
|
213
|
+
const high = toFiniteNumberOrNull(candle.high);
|
|
214
|
+
const low = toFiniteNumberOrNull(candle.low);
|
|
215
|
+
if (timestamp == null || close == null || high == null || low == null) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const linePrice = evaluator.evaluate(timestamp);
|
|
219
|
+
const priceVsLinePct = linePrice !== 0 ? (close - linePrice) / linePrice * 100 : null;
|
|
220
|
+
const priceVsLinePctAbs = priceVsLinePct == null ? null : Math.abs(priceVsLinePct);
|
|
221
|
+
const breakoutSideClose = getBreakoutSide({
|
|
222
|
+
direction: structural.signalDirection,
|
|
223
|
+
priceVsLinePct
|
|
224
|
+
});
|
|
225
|
+
const clearBreakClose = getClearBreakAtPct({
|
|
226
|
+
direction: structural.signalDirection,
|
|
227
|
+
priceVsLinePct
|
|
228
|
+
});
|
|
229
|
+
const nearLineClose = priceVsLinePctAbs == null ? null : priceVsLinePctAbs < TRENDLINE_CLEAR_BREAK_PCT;
|
|
230
|
+
const lineTouched = low <= linePrice && high >= linePrice;
|
|
231
|
+
const distanceAtrRatio = priceVsLinePctAbs != null && atrValues[index] != null && atrValues[index] > 0 ? priceVsLinePctAbs / atrValues[index] : null;
|
|
232
|
+
return {
|
|
233
|
+
timestamp,
|
|
234
|
+
linePrice,
|
|
235
|
+
priceVsLinePct,
|
|
236
|
+
priceVsLinePctAbs,
|
|
237
|
+
breakoutSideClose,
|
|
238
|
+
clearBreakClose,
|
|
239
|
+
nearLineClose,
|
|
240
|
+
lineTouched,
|
|
241
|
+
distanceAtrRatio
|
|
242
|
+
};
|
|
243
|
+
}).filter(Boolean) : [];
|
|
244
|
+
const lastSample = recentSamples.length > 0 ? recentSamples[recentSamples.length - 1] : null;
|
|
245
|
+
const prevSample = recentSamples.length > 1 ? recentSamples[recentSamples.length - 2] : null;
|
|
246
|
+
const prevPrevSample = recentSamples.length > 2 ? recentSamples[recentSamples.length - 3] : null;
|
|
247
|
+
let latestLineCrossIndex = null;
|
|
248
|
+
let latestClearBreakIndex = null;
|
|
249
|
+
for (let index = 0; index < recentSamples.length; index += 1) {
|
|
250
|
+
const sample = recentSamples[index];
|
|
251
|
+
const prev = index > 0 ? recentSamples[index - 1] : null;
|
|
252
|
+
if (sample.breakoutSideClose === true && (prev == null || prev.breakoutSideClose !== true)) {
|
|
253
|
+
latestLineCrossIndex = index;
|
|
254
|
+
}
|
|
255
|
+
if (sample.clearBreakClose === true && (prev == null || prev.clearBreakClose !== true)) {
|
|
256
|
+
latestClearBreakIndex = index;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
let latestRetestIndex = null;
|
|
260
|
+
if (latestLineCrossIndex != null) {
|
|
261
|
+
for (let index = latestLineCrossIndex + 1; index < recentSamples.length - 1; index += 1) {
|
|
262
|
+
const sample = recentSamples[index];
|
|
263
|
+
if (sample.lineTouched || sample.nearLineClose === true) {
|
|
264
|
+
latestRetestIndex = index;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const currentIndex = recentSamples.length - 1;
|
|
269
|
+
const barsSinceLineCross = latestLineCrossIndex != null ? currentIndex - latestLineCrossIndex : null;
|
|
270
|
+
const barsSinceClearBreak = latestClearBreakIndex != null ? currentIndex - latestClearBreakIndex : null;
|
|
271
|
+
const barsSinceRetest = latestRetestIndex != null ? currentIndex - latestRetestIndex : null;
|
|
272
|
+
const retestHappened = latestRetestIndex != null;
|
|
273
|
+
const retestConfirmed = retestHappened === true && barsSinceRetest != null && barsSinceRetest > 0 && lastSample?.clearBreakClose === true;
|
|
274
|
+
const breakoutFresh = barsSinceLineCross != null && barsSinceLineCross >= 0 && barsSinceLineCross <= 1;
|
|
275
|
+
const staleBreakout = lastSample?.clearBreakClose === true && barsSinceLineCross != null && barsSinceLineCross > 1 && retestConfirmed !== true;
|
|
276
|
+
const currentDistanceAtrRatio = lastSample?.distanceAtrRatio ?? null;
|
|
277
|
+
const previousDistanceAtrRatio = prevSample?.distanceAtrRatio ?? null;
|
|
278
|
+
const distanceAtrVelocity = currentDistanceAtrRatio != null && previousDistanceAtrRatio != null ? currentDistanceAtrRatio - previousDistanceAtrRatio : null;
|
|
279
|
+
const distanceAtrAcceleration = currentDistanceAtrRatio != null && previousDistanceAtrRatio != null && prevPrevSample?.distanceAtrRatio != null ? currentDistanceAtrRatio - 2 * previousDistanceAtrRatio + prevPrevSample.distanceAtrRatio : null;
|
|
280
|
+
const distanceAtrRecent = recentSamples.map((sample) => sample.distanceAtrRatio).filter((value) => value != null);
|
|
281
|
+
const maxDistanceAtrRatioRecent = distanceAtrRecent.length > 0 ? Math.max(...distanceAtrRecent) : null;
|
|
282
|
+
const minDistanceAtrRatioRecent = distanceAtrRecent.length > 0 ? Math.min(...distanceAtrRecent) : null;
|
|
283
|
+
const firstPoint = evaluator?.firstPoint ?? null;
|
|
284
|
+
const lastPoint = evaluator?.lastPoint ?? null;
|
|
285
|
+
const intervalMs = sortedCandles.length > 1 ? toFiniteNumberOrNull(
|
|
286
|
+
sortedCandles[sortedCandles.length - 1].timestamp
|
|
287
|
+
) - toFiniteNumberOrNull(
|
|
288
|
+
sortedCandles[sortedCandles.length - 2].timestamp
|
|
289
|
+
) : null;
|
|
290
|
+
const lineBarsSpan = firstPoint != null && lastPoint != null && intervalMs != null && intervalMs > 0 ? Math.max(
|
|
291
|
+
1,
|
|
292
|
+
Math.round((lastPoint.timestamp - firstPoint.timestamp) / intervalMs)
|
|
293
|
+
) : null;
|
|
294
|
+
const lineSlopePct = firstPoint != null && lastPoint != null && firstPoint.value !== 0 ? (lastPoint.value - firstPoint.value) / firstPoint.value * 100 : null;
|
|
295
|
+
const lineSlopePctPerBar = lineSlopePct != null && lineBarsSpan != null && lineBarsSpan > 0 ? lineSlopePct / lineBarsSpan : null;
|
|
296
|
+
const lineSlopeDirection = getLineSlopeDirection(lineSlopePctPerBar);
|
|
297
|
+
const lineSlopeAligned = lineSlopeDirection == null || structural.mode == null ? null : structural.mode === "lows" ? lineSlopeDirection === "rising" : structural.mode === "highs" ? lineSlopeDirection === "falling" : null;
|
|
298
|
+
let entryTiming = "unknown";
|
|
299
|
+
if (lastSample?.clearBreakClose === true) {
|
|
300
|
+
if (retestConfirmed) {
|
|
301
|
+
entryTiming = "ready_retest";
|
|
302
|
+
} else if (barsSinceLineCross === 0) {
|
|
303
|
+
entryTiming = "ready_breakout";
|
|
304
|
+
} else if (barsSinceLineCross === 1 && (distanceAtrVelocity == null || distanceAtrVelocity >= 0)) {
|
|
305
|
+
entryTiming = "ready_follow_through";
|
|
306
|
+
} else if (retestHappened) {
|
|
307
|
+
entryTiming = "wait_retest_confirmation";
|
|
308
|
+
} else if (staleBreakout) {
|
|
309
|
+
entryTiming = "stale_breakout";
|
|
310
|
+
} else {
|
|
311
|
+
entryTiming = "wait_retest";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
lineCrossDetected: latestLineCrossIndex != null,
|
|
316
|
+
clearBreakDetected: latestClearBreakIndex != null,
|
|
317
|
+
barsSinceLineCross,
|
|
318
|
+
barsSinceClearBreak,
|
|
319
|
+
barsSinceRetest,
|
|
320
|
+
breakoutFresh,
|
|
321
|
+
retestHappened,
|
|
322
|
+
retestConfirmed,
|
|
323
|
+
staleBreakout,
|
|
324
|
+
entryTiming,
|
|
325
|
+
entryReadyNow: entryTiming === "ready_breakout" || entryTiming === "ready_follow_through" || entryTiming === "ready_retest",
|
|
326
|
+
lineSlopePct,
|
|
327
|
+
lineSlopePctPerBar,
|
|
328
|
+
lineSlopeDirection,
|
|
329
|
+
lineSlopeAligned,
|
|
330
|
+
currentDistanceAtrRatio,
|
|
331
|
+
previousDistanceAtrRatio,
|
|
332
|
+
distanceAtrVelocity,
|
|
333
|
+
distanceAtrAcceleration,
|
|
334
|
+
maxDistanceAtrRatioRecent,
|
|
335
|
+
minDistanceAtrRatioRecent
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// src/TrendLine/adapters/ai.ts
|
|
340
|
+
var TRENDLINE_CONTEXT_PROMPT = `
|
|
341
|
+
\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F trendline-\u0441\u0435\u0442\u0430\u043F\u043E\u0432:
|
|
342
|
+
- \u042D\u0442\u043E \u0441\u0435\u0442\u0430\u043F \u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0435 \u043F\u0440\u043E\u0431\u043E\u044F/\u0440\u0435\u0430\u043A\u0446\u0438\u0438 \u043E\u0442 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438; \u043F\u043E\u043B\u0435 payload.figures.trendline \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044E \u044D\u0442\u043E\u0439 \u043B\u0438\u043D\u0438\u0438, \u0430 payload.additionalIndicators.trendlineContext \u2014 \u043A\u0440\u0430\u0442\u043A\u0443\u044E \u0441\u0432\u043E\u0434\u043A\u0443 \u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0446\u0435\u043D\u044B \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043B\u0438\u043D\u0438\u0438.
|
|
343
|
+
- \u0414\u043B\u044F TrendLine \u0440\u043E\u043B\u044C \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0438/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0446\u0435\u043D\u044B \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u043D\u044B\u0445 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439.
|
|
344
|
+
- \u041A\u0430\u0441\u0430\u043D\u0438\u044F \u0443\u0441\u0438\u043B\u0438\u0432\u0430\u044E\u0442 \u043B\u0438\u043D\u0438\u044E, \u043D\u043E \u0441\u0430\u043C\u0438 \u043F\u043E \u0441\u0435\u0431\u0435 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E\u0442 \u0441\u0438\u0433\u043D\u0430\u043B. \u0411\u0435\u0437 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F/\u0440\u0435\u0442\u0435\u0441\u0442\u0430 \u043D\u0435 \u043F\u043E\u0432\u044B\u0448\u0430\u0439 quality \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437-\u0437\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043A\u0430\u0441\u0430\u043D\u0438\u0439.
|
|
345
|
+
- \u0414\u043B\u044F SHORT \u043F\u043E rising support (trendline.mode="lows") \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0443\u0436\u0435\u043D \u043B\u0438\u0431\u043E \u044F\u0432\u043D\u044B\u0439 \u0443\u0445\u043E\u0434 \u0446\u0435\u043D\u044B \u043D\u0438\u0436\u0435 \u043B\u0438\u043D\u0438\u0438, \u043B\u0438\u0431\u043E \u0440\u0435\u0442\u0435\u0441\u0442 \u043B\u0438\u043D\u0438\u0438 \u0441\u043D\u0438\u0437\u0443 \u0441 \u043E\u0442\u0431\u043E\u0435\u043C. \u0415\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043E\u0441\u0442\u0430\u0435\u0442\u0441\u044F \u043D\u0430\u0434 \u043B\u0438\u043D\u0438\u0435\u0439 \u0438\u043B\u0438 \u043F\u0440\u044F\u043C\u043E \u043D\u0430 \u043D\u0435\u0439, \u043E\u0431\u044B\u0447\u043D\u043E direction=null \u0438 quality <= 2.
|
|
346
|
+
- \u0414\u043B\u044F LONG \u043F\u043E descending resistance (trendline.mode="highs") \u0437\u0435\u0440\u043A\u0430\u043B\u044C\u043D\u043E: \u043D\u0443\u0436\u0435\u043D \u0432\u044B\u0445\u043E\u0434 \u0432\u044B\u0448\u0435 \u043B\u0438\u043D\u0438\u0438 \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442 \u0441\u0432\u0435\u0440\u0445\u0443. \u0415\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043F\u043E\u0434 \u043B\u0438\u043D\u0438\u0435\u0439 \u0438\u043B\u0438 \u043F\u0440\u044F\u043C\u043E \u043D\u0430 \u043D\u0435\u0439, \u043E\u0431\u044B\u0447\u043D\u043E direction=null \u0438 quality <= 2.
|
|
347
|
+
- \u0415\u0441\u043B\u0438 payload.additionalIndicators.trendlineContext.nearLineNoise=true, \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u044D\u0442\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C \u043F\u0440\u043E\u0431\u043E\u0435\u043C: \u0447\u0430\u0449\u0435 quality <= 2-3 \u0438 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0440\u0435\u0442\u0435\u0441\u0442\u0430/\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
|
|
348
|
+
- \u0415\u0441\u043B\u0438 payload.additionalIndicators.trendlineContext.coinBiasAligned=false \u0438\u043B\u0438 btcBiasAligned=false, \u0442\u0440\u0430\u043A\u0442\u0443\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u043F\u0440\u044F\u043C\u043E\u0439 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u0430. \u0412 \u0442\u0430\u043A\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C, \u0435\u0441\u043B\u0438 \u043D\u0435\u0442 \u0438\u0441\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0433\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0433\u043E \u043F\u0440\u0435\u0438\u043C\u0443\u0449\u0435\u0441\u0442\u0432\u0430.
|
|
349
|
+
- \u0415\u0441\u043B\u0438 payload.additionalIndicators.trendlineContext.clearBreak=false \u0438 \u0446\u0435\u043D\u0430 \u0432\u0441\u0435 \u0435\u0449\u0435 \u043E\u043A\u043E\u043B\u043E \u043B\u0438\u043D\u0438\u0438, \u043D\u0435 \u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u043A\u0430\u043A "\u0447\u0438\u0441\u0442\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439".
|
|
350
|
+
- \u0415\u0441\u043B\u0438 clearBreak=true, \u043D\u043E trendlineContext.weakCleanBreak=true, \u0442\u0440\u0430\u043A\u0442\u0443\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439: \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u0443\u0436\u0435 \u0437\u0430\u0434\u0435\u043B\u043E, \u043D\u043E \u0437\u0430\u043F\u0430\u0441\u0430 \u043F\u043E displacement \u043F\u043E\u043A\u0430 \u043C\u0430\u043B\u043E. \u041E\u0431\u044B\u0447\u043D\u043E \u0437\u0434\u0435\u0441\u044C \u043D\u0443\u0436\u0435\u043D follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
|
|
351
|
+
- \u0415\u0441\u043B\u0438 clearBreak=true, \u043D\u043E trendlineContext.compressedCleanBreak=true, \u044D\u0442\u043E \u0441\u0436\u0430\u0442\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u043F\u043E\u0441\u043B\u0435 \u0441\u0435\u0440\u0438\u0438 \u0431\u043B\u0438\u0437\u043A\u0438\u0445 \u043A\u0430\u0441\u0430\u043D\u0438\u0439 \u043D\u0430 \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0439 \u043B\u0438\u043D\u0438\u0438. \u0414\u0430\u0436\u0435 \u043F\u0440\u0438 \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u043E\u043C \u0432\u044B\u0445\u043E\u0434\u0435 \u0437\u0430 \u043B\u0438\u043D\u0438\u044E \u0437\u0434\u0435\u0441\u044C \u0447\u0430\u0449\u0435 \u043D\u0443\u0436\u0435\u043D follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
|
|
352
|
+
- \u0415\u0441\u043B\u0438 clearBreak=true, \u043D\u043E trendlineContext.breakVsAtrRatio < 0.5 \u0438 \u043F\u0440\u0438 \u044D\u0442\u043E\u043C trendlineContext.weakBtcLedBreak=true, \u0441\u0447\u0438\u0442\u0430\u0439 \u044D\u0442\u043E \u0441\u043B\u0430\u0431\u044B\u043C BTC-led \u043F\u0440\u043E\u0431\u043E\u0435\u043C \u0431\u0435\u0437 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0433\u043E follow-through \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435. \u041E\u0431\u044B\u0447\u043D\u043E \u0437\u0434\u0435\u0441\u044C \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u0442\u0435\u0441\u0442/\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
|
|
353
|
+
- \u0414\u043B\u044F LONG \u043F\u043E descending resistance, \u0435\u0441\u043B\u0438 \u043B\u0438\u043D\u0438\u044F \u043E\u0447\u0435\u043D\u044C \u0434\u043B\u0438\u043D\u043D\u0430\u044F, \u0430 \u0432\u044B\u0445\u043E\u0434 \u043D\u0430\u0434 \u043D\u0435\u0439 \u043F\u043E\u043A\u0430 \u0443\u043C\u0435\u0440\u0435\u043D\u043D\u044B\u0439 \u0438 BTC \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043F\u0440\u043E\u0431\u043E\u0439 \u0441\u043B\u0430\u0431\u043E, \u0442\u0440\u0430\u043A\u0442\u0443\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u0440\u0430\u043D\u043D\u0438\u0439 breakout \u0431\u0435\u0437 follow-through. \u0412 \u0442\u0430\u043A\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0447\u0430\u0449\u0435 \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u0442\u0435\u0441\u0442/\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
|
|
354
|
+
- \u0414\u043B\u044F TrendLine quality 4-5 \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u043E\u0433\u0434\u0430 \u043E\u0434\u043D\u043E\u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E: clearBreak=true, nearLineNoise=false, coinBiasAligned=true \u0438 btcBiasAligned=true. \u0415\u0441\u043B\u0438 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u043D\u043E \u0438\u0437 \u044D\u0442\u0438\u0445 \u0443\u0441\u043B\u043E\u0432\u0438\u0439 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043E, \u043D\u0435 \u0441\u0442\u0430\u0432\u044C quality \u0432\u044B\u0448\u0435 3.
|
|
355
|
+
- \u0420\u0435\u0434\u043A\u043E\u0435 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: \u0435\u0441\u043B\u0438 trendlineContext.aggressivePreBreakPressure=true, \u044D\u0442\u043E \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043D\u044B\u0439 pre-break pressure \u0441\u0435\u0442\u0430\u043F. \u0412 \u0442\u0430\u043A\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C quality=4 \u0434\u0430\u0436\u0435 \u0431\u0435\u0437 clearBreak, \u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u043A \u0440\u0430\u043D\u043D\u0435\u0435 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043D\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0442 coin/BTC bias.
|
|
356
|
+
- \u0415\u0449\u0435 \u043E\u0434\u043D\u043E \u0440\u0435\u0434\u043A\u043E\u0435 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: \u0435\u0441\u043B\u0438 trendlineContext.strongNearBreakPressure=true, \u044D\u0442\u043E \u0437\u0440\u0435\u043B\u0430\u044F \u043B\u0438\u043D\u0438\u044F \u0441 \u0443\u0436\u0435 \u043D\u0430\u0447\u0430\u0432\u0448\u0438\u043C\u0441\u044F \u043F\u0440\u043E\u0434\u0430\u0432\u043B\u0438\u0432\u0430\u043D\u0438\u0435\u043C \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u0438 \u043E\u0447\u0435\u043D\u044C \u0441\u0438\u043B\u044C\u043D\u044B\u043C aligned pressure \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC. \u0412 \u0442\u0430\u043A\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C quality=4 \u0434\u0430\u0436\u0435 \u043F\u0440\u0438 nearLineNoise=true, \u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u043A \u0440\u0430\u043D\u043D\u0435\u0435 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E \u0441\u0438\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435.
|
|
357
|
+
`;
|
|
358
|
+
var TRENDLINE_PAYLOAD_PROMPT = `
|
|
359
|
+
- \u0412 payload.figures.trendline \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u043F\u043E\u043B\u043D\u0430\u044F \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044F \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 (\u0431\u0435\u0437 trim), \u0447\u0442\u043E\u0431\u044B \u043C\u043E\u0436\u043D\u043E \u0431\u044B\u043B\u043E \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0442\u044C \u043A\u0430\u0441\u0430\u043D\u0438\u044F/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443.
|
|
360
|
+
- \u0412 payload.additionalIndicators.trendlineContext \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F mode / touches / distance / currentLinePrice / priceVsLinePct / priceVsLineSide / clearBreak / nearLineNoise / coinMaBias / btcMaBias / maxAllowedQuality / approvalAllowedNow / hardBlockReasons.
|
|
361
|
+
- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0432 trendlineContext \u043F\u0435\u0440\u0435\u0434\u0430\u044E\u0442\u0441\u044F atrPct / breakVsAtrRatio / coinMaSpreadPct / btcMaSpreadPct / aggressivePreBreakPressure / strongNearBreakPressure / weakCleanBreak / compressedCleanBreak / weakBtcLedBreak / weakLongFarBreak.
|
|
362
|
+
`;
|
|
363
|
+
var buildTrendlineContext = (signal) => {
|
|
364
|
+
const structural = buildTrendlineStructuralContext(signal);
|
|
365
|
+
const trendLine = getTrendLineFromPayload(signal);
|
|
366
|
+
const coinMaFast = getLastFiniteNumber(signal.indicators?.maFast);
|
|
367
|
+
const coinMaSlow = getLastFiniteNumber(signal.indicators?.maSlow);
|
|
368
|
+
const coinMaBias = getBias(coinMaFast, coinMaSlow);
|
|
369
|
+
const coinMaSpreadPct = getSpreadPct(coinMaFast, coinMaSlow);
|
|
370
|
+
const entryTiming = typeof signal.additionalIndicators?.trendlineTiming === "object" && signal.additionalIndicators?.trendlineTiming && typeof signal.additionalIndicators.trendlineTiming.entryTiming === "string" ? signal.additionalIndicators.trendlineTiming.entryTiming : null;
|
|
371
|
+
const coinBiasAligned = structural.signalDirection == null || coinMaBias == null ? null : structural.signalDirection === "SHORT" ? coinMaBias === "bearish" : coinMaBias === "bullish";
|
|
372
|
+
const aggressivePreBreakPressure = structural.signalDirection === "SHORT" && trendLine?.mode === "lows" && structural.priceVsLinePct != null && structural.priceVsLinePct > 0 && structural.priceVsLinePct <= 0.15 && (structural.touches ?? 0) >= 5 && structural.distance != null && structural.distance >= 90 && structural.distance <= 120 && coinBiasAligned === true && structural.btcBiasAligned === true && coinMaSpreadPct != null && coinMaSpreadPct <= -1 && structural.btcMaSpreadPct != null && structural.btcMaSpreadPct <= -0.3;
|
|
373
|
+
const strongNearBreakPressure = structural.signalDirection === "SHORT" && trendLine?.mode === "lows" && structural.clearBreak === false && structural.nearLineNoise === true && structural.priceVsLinePct != null && structural.priceVsLinePct < 0 && structural.breakVsAtrRatio != null && structural.breakVsAtrRatio >= 0.25 && structural.breakVsAtrRatio <= 0.35 && coinBiasAligned === true && structural.btcBiasAligned === true && coinMaSpreadPct != null && coinMaSpreadPct <= -1.5 && structural.btcMaSpreadPct != null && structural.btcMaSpreadPct <= -0.5 && (structural.touches ?? 0) >= 5 && structural.distance != null && structural.distance >= 300;
|
|
374
|
+
const weakBtcLedBreak = structural.signalDirection === "SHORT" ? structural.clearBreak === true && structural.breakVsAtrRatio != null && structural.breakVsAtrRatio < 0.5 && coinBiasAligned === true && structural.btcBiasAligned === true && coinMaSpreadPct != null && coinMaSpreadPct > -0.6 && structural.btcMaSpreadPct != null && structural.btcMaSpreadPct <= -0.3 : structural.signalDirection === "LONG" ? structural.clearBreak === true && structural.breakVsAtrRatio != null && structural.breakVsAtrRatio < 0.5 && coinBiasAligned === true && structural.btcBiasAligned === true && coinMaSpreadPct != null && coinMaSpreadPct < 0.6 && structural.btcMaSpreadPct != null && structural.btcMaSpreadPct >= 0.3 : false;
|
|
375
|
+
const hardBlockReasons = [...structural.structuralHardBlockReasons];
|
|
376
|
+
if (coinBiasAligned === false) {
|
|
377
|
+
hardBlockReasons.push("coin_bias_conflict");
|
|
378
|
+
}
|
|
379
|
+
if (structural.btcBiasAligned === false) {
|
|
380
|
+
hardBlockReasons.push("btc_bias_conflict");
|
|
381
|
+
}
|
|
382
|
+
if (weakBtcLedBreak) {
|
|
383
|
+
hardBlockReasons.push("weak_btc_led_break");
|
|
384
|
+
}
|
|
385
|
+
const deterministicQuality = getDeterministicTrendlineQuality({
|
|
386
|
+
signalDirection: structural.signalDirection,
|
|
387
|
+
clearBreak: structural.clearBreak,
|
|
388
|
+
nearLineNoise: structural.nearLineNoise,
|
|
389
|
+
breakVsAtrRatio: structural.breakVsAtrRatio,
|
|
390
|
+
priceVsLinePctAbs: structural.priceVsLinePctAbs,
|
|
391
|
+
touches: structural.touches,
|
|
392
|
+
distance: structural.distance,
|
|
393
|
+
btcMaSpreadPct: structural.btcMaSpreadPct,
|
|
394
|
+
aggressivePreBreakPressure,
|
|
395
|
+
strongNearBreakPressure,
|
|
396
|
+
hardBlockReasons,
|
|
397
|
+
entryTiming,
|
|
398
|
+
coinMaSpreadPct
|
|
399
|
+
});
|
|
400
|
+
const maxAllowedQuality = deterministicQuality;
|
|
401
|
+
const approvalAllowedNow = deterministicQuality >= 4;
|
|
402
|
+
return {
|
|
403
|
+
...structural,
|
|
404
|
+
entryTiming,
|
|
405
|
+
coinMaFast,
|
|
406
|
+
coinMaSlow,
|
|
407
|
+
coinMaBias,
|
|
408
|
+
coinMaSpreadPct,
|
|
409
|
+
coinBiasAligned,
|
|
410
|
+
aggressivePreBreakPressure,
|
|
411
|
+
strongNearBreakPressure,
|
|
412
|
+
weakBtcLedBreak,
|
|
413
|
+
deterministicQuality,
|
|
414
|
+
maxAllowedQuality,
|
|
415
|
+
approvalAllowedNow,
|
|
416
|
+
hardBlockReasons
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
var formatPromptNumber = (value, fractionDigits = 4) => {
|
|
420
|
+
if (value == null) {
|
|
421
|
+
return "n/a";
|
|
422
|
+
}
|
|
423
|
+
return value.toFixed(fractionDigits);
|
|
424
|
+
};
|
|
425
|
+
var getHardBlockReasonText = (reason) => {
|
|
426
|
+
switch (reason) {
|
|
427
|
+
case "no_clear_break":
|
|
428
|
+
return "\u043D\u0435\u0442 \u0447\u0438\u0441\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F \u043B\u0438\u043D\u0438\u0438";
|
|
429
|
+
case "near_line_noise":
|
|
430
|
+
return "\u0446\u0435\u043D\u0430 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0431\u043B\u0438\u0437\u043A\u043E \u043A \u043B\u0438\u043D\u0438\u0438 \u0438 \u044D\u0442\u043E \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 \u0448\u0443\u043C";
|
|
431
|
+
case "coin_bias_conflict":
|
|
432
|
+
return "bias \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C";
|
|
433
|
+
case "btc_bias_conflict":
|
|
434
|
+
return "BTC-\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C";
|
|
435
|
+
case "weak_clean_break":
|
|
436
|
+
return "\u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u0435\u0441\u0442\u044C, \u043D\u043E displacement \u0435\u0449\u0435 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u044B\u0439 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E ATR";
|
|
437
|
+
case "compressed_clean_break":
|
|
438
|
+
return "\u043F\u0440\u043E\u0431\u043E\u0439 \u0432\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u0436\u0430\u0442\u044B\u043C: \u0441\u0435\u0440\u0438\u044F \u0431\u043B\u0438\u0437\u043A\u0438\u0445 \u043A\u0430\u0441\u0430\u043D\u0438\u0439 \u043D\u0430 \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 \u0431\u0435\u0437 \u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E\u0433\u043E follow-through";
|
|
439
|
+
case "weak_btc_led_break":
|
|
440
|
+
return "\u043F\u0440\u043E\u0431\u043E\u0439 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u0435\u043B\u043A\u0438\u0439 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E ATR \u0438 \u0431\u043E\u043B\u044C\u0448\u0435 \u043F\u043E\u0445\u043E\u0436 \u043D\u0430 BTC-led \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u0435 \u0431\u0435\u0437 follow-through \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435";
|
|
441
|
+
case "weak_long_far_break":
|
|
442
|
+
return "\u0434\u043B\u044F LONG \u043F\u0440\u043E\u0431\u043E\u0439 \u043E\u0447\u0435\u043D\u044C \u0434\u043B\u0438\u043D\u043D\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 \u043F\u043E\u043A\u0430 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0443\u043C\u0435\u0440\u0435\u043D\u043D\u044B\u0439, \u0430 BTC \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0435\u0433\u043E \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u043E";
|
|
443
|
+
default:
|
|
444
|
+
return reason;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
var mergeShortText = (primary, fallback, maxLength) => {
|
|
448
|
+
const value = primary.trim() || fallback;
|
|
449
|
+
return value.slice(0, maxLength);
|
|
450
|
+
};
|
|
451
|
+
var getDeterministicTrendlineQuality = (trendlineContext) => {
|
|
452
|
+
if (trendlineContext.aggressivePreBreakPressure === true || trendlineContext.strongNearBreakPressure === true) {
|
|
453
|
+
return 4;
|
|
454
|
+
}
|
|
455
|
+
if (trendlineContext.hardBlockReasons.length > 0) {
|
|
456
|
+
return trendlineContext.clearBreak === true ? 3 : 2;
|
|
457
|
+
}
|
|
458
|
+
if (trendlineContext.clearBreak !== true || trendlineContext.nearLineNoise !== false || trendlineContext.signalDirection == null) {
|
|
459
|
+
return 2;
|
|
460
|
+
}
|
|
461
|
+
const breakVsAtrRatio = trendlineContext.breakVsAtrRatio ?? 0;
|
|
462
|
+
const priceVsLinePctAbs = trendlineContext.priceVsLinePctAbs ?? 0;
|
|
463
|
+
const touches = trendlineContext.touches ?? 0;
|
|
464
|
+
const distance = trendlineContext.distance ?? Number.POSITIVE_INFINITY;
|
|
465
|
+
const btcMaSpreadPct = trendlineContext.btcMaSpreadPct ?? 0;
|
|
466
|
+
const coinMaSpreadPct = trendlineContext.coinMaSpreadPct ?? 0;
|
|
467
|
+
const entryTiming = trendlineContext.entryTiming;
|
|
468
|
+
if (trendlineContext.signalDirection === "LONG") {
|
|
469
|
+
const quality52 = breakVsAtrRatio >= 1.1 && priceVsLinePctAbs >= 1 && touches >= 5 && distance < 250 && btcMaSpreadPct >= 0.5;
|
|
470
|
+
if (quality52) {
|
|
471
|
+
return 5;
|
|
472
|
+
}
|
|
473
|
+
const compactBreakoutQuality4 = breakVsAtrRatio >= 0.75 && priceVsLinePctAbs >= 0.7 && distance < 150 && (btcMaSpreadPct >= 0.4 || breakVsAtrRatio >= 1) && (touches >= 5 || breakVsAtrRatio >= 0.85);
|
|
474
|
+
const shortLineStrengthQuality4 = breakVsAtrRatio >= 0.6 && priceVsLinePctAbs >= 0.65 && touches >= 6 && distance < 120 && btcMaSpreadPct >= 0.75;
|
|
475
|
+
const matureLineQuality4 = breakVsAtrRatio >= 0.8 && priceVsLinePctAbs >= 0.7 && touches >= 5 && distance < 350 && btcMaSpreadPct >= 0.4;
|
|
476
|
+
const extendedHighConvictionQuality4 = breakVsAtrRatio >= 0.75 && priceVsLinePctAbs >= 0.65 && touches >= 5 && distance < 600 && btcMaSpreadPct >= 0.9;
|
|
477
|
+
return compactBreakoutQuality4 || shortLineStrengthQuality4 || matureLineQuality4 || extendedHighConvictionQuality4 ? 4 : 3;
|
|
478
|
+
}
|
|
479
|
+
const quality5 = breakVsAtrRatio >= 5 && priceVsLinePctAbs >= 10 && touches >= 5 && distance >= 240 && distance <= 400 && btcMaSpreadPct <= -1;
|
|
480
|
+
if (quality5) {
|
|
481
|
+
return 5;
|
|
482
|
+
}
|
|
483
|
+
const quality4 = breakVsAtrRatio >= 1 && breakVsAtrRatio < 2.5 && priceVsLinePctAbs >= 1 && touches >= 5 && distance < 300 && btcMaSpreadPct <= -0.5;
|
|
484
|
+
const strongReadyBreakoutQuality4 = entryTiming === "ready_breakout" && breakVsAtrRatio >= 2 && priceVsLinePctAbs >= 1.8 && touches >= 5 && btcMaSpreadPct <= -1 && (coinMaSpreadPct <= -1 || breakVsAtrRatio >= 3);
|
|
485
|
+
return quality4 || strongReadyBreakoutQuality4 ? 4 : 3;
|
|
486
|
+
};
|
|
487
|
+
var getDeterministicTrendlineQualityReason = (trendlineContext) => {
|
|
488
|
+
if (trendlineContext.hardBlockReasons.length > 0) {
|
|
489
|
+
return `TrendLine guardrail: \u0432\u0445\u043E\u0434 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.`;
|
|
490
|
+
}
|
|
491
|
+
if (trendlineContext.signalDirection === "LONG") {
|
|
492
|
+
return "TrendLine deterministic quality: \u043F\u0440\u043E\u0431\u043E\u0439 \u0435\u0441\u0442\u044C, \u043D\u043E \u0434\u043B\u044F LONG \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 displacement, \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438 BTC \u0438\u043B\u0438 \u043B\u0438\u043D\u0438\u044F \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0434\u043B\u0438\u043D\u043D\u0430\u044F \u0434\u043B\u044F \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0433\u043E \u0432\u0445\u043E\u0434\u0430.";
|
|
493
|
+
}
|
|
494
|
+
if (trendlineContext.signalDirection === "SHORT") {
|
|
495
|
+
return "TrendLine deterministic quality: \u043F\u0440\u043E\u0431\u043E\u0439 \u0435\u0441\u0442\u044C, \u043D\u043E \u0434\u043B\u044F SHORT \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 bearish displacement \u0438\u043B\u0438 follow-through, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0432\u0445\u043E\u0434 \u043F\u043E\u043A\u0430 \u0440\u0430\u043D\u043E \u043E\u0434\u043E\u0431\u0440\u044F\u0442\u044C.";
|
|
496
|
+
}
|
|
497
|
+
return "TrendLine deterministic quality: \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0435\u0449\u0435 \u043D\u0435 \u0434\u043E\u0442\u044F\u0433\u0438\u0432\u0430\u0435\u0442 \u0434\u043E \u0432\u0445\u043E\u0434\u0430 \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441.";
|
|
498
|
+
};
|
|
499
|
+
var getTrendlineContextFromPayload = (payload, signal) => {
|
|
500
|
+
const additional = payload.additionalIndicators;
|
|
501
|
+
const fromPayload = additional?.trendlineContext;
|
|
502
|
+
return fromPayload ?? buildTrendlineContext(signal);
|
|
503
|
+
};
|
|
504
|
+
var trendLineAiAdapter = {
|
|
505
|
+
// Shared builder trims nested series/figures; TrendLine keeps trendline geometry untrimmed on purpose.
|
|
506
|
+
buildPayload: ({ signal, basePayload }) => ({
|
|
507
|
+
...basePayload,
|
|
508
|
+
figures: {
|
|
509
|
+
...basePayload.figures,
|
|
510
|
+
// Keep raw line geometry available exactly where the shared prompt expects it.
|
|
511
|
+
trendline: getTrendLineFromPayload(signal)
|
|
512
|
+
},
|
|
513
|
+
additionalIndicators: {
|
|
514
|
+
...basePayload.additionalIndicators,
|
|
515
|
+
trendlineContext: buildTrendlineContext(signal)
|
|
516
|
+
}
|
|
517
|
+
}),
|
|
518
|
+
postProcessAnalysis: ({ signal, payload, analysis }) => {
|
|
519
|
+
const trendlineContext = getTrendlineContextFromPayload(payload, signal);
|
|
520
|
+
const quality = trendlineContext.deterministicQuality;
|
|
521
|
+
const signalDirection = signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
|
|
522
|
+
if ((trendlineContext.aggressivePreBreakPressure === true || trendlineContext.strongNearBreakPressure === true) && signalDirection != null) {
|
|
523
|
+
const fallbackReason = trendlineContext.strongNearBreakPressure === true ? "TrendLine strong near-break pressure: \u0437\u0440\u0435\u043B\u0430\u044F \u043B\u0438\u043D\u0438\u044F \u0443\u0436\u0435 \u043F\u0440\u043E\u0434\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0441\u0434\u0435\u043B\u043A\u0438, \u0440\u0430\u043D\u043D\u0438\u0439 \u0432\u0445\u043E\u0434 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D \u043A\u043E\u0434\u043E\u043C \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438." : "TrendLine aggressive pre-break pressure: \u0440\u0430\u043D\u043D\u0435\u0435 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u043F\u0440\u0438 \u0441\u0438\u043B\u044C\u043D\u043E\u043C bearish pressure.";
|
|
524
|
+
const fallbackComment = trendlineContext.strongNearBreakPressure === true ? "TrendLine strong near-break pressure: \u0440\u0430\u043D\u043D\u0438\u0439 \u0432\u0445\u043E\u0434 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D \u043A\u043E\u0434\u043E\u043C \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438." : "TrendLine aggressive pre-break pressure: \u0440\u0430\u043D\u043D\u0438\u0439 \u0432\u0445\u043E\u0434 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D \u043A\u043E\u0434\u043E\u043C \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438.";
|
|
525
|
+
return {
|
|
526
|
+
...analysis,
|
|
527
|
+
direction: signalDirection,
|
|
528
|
+
quality: 4,
|
|
529
|
+
needRetest: false,
|
|
530
|
+
retestPrice: null,
|
|
531
|
+
takeProfitPrice: analysis.takeProfitPrice ?? signal.prices?.takeProfitPrice ?? null,
|
|
532
|
+
stopLossPrice: analysis.stopLossPrice ?? signal.prices?.stopLossPrice ?? null,
|
|
533
|
+
qualityReason: mergeShortText(
|
|
534
|
+
analysis.qualityReason ?? "",
|
|
535
|
+
fallbackReason,
|
|
536
|
+
400
|
|
537
|
+
),
|
|
538
|
+
comment: mergeShortText(analysis.comment ?? "", fallbackComment, 1024)
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (trendlineContext.approvalAllowedNow === true && signalDirection != null) {
|
|
542
|
+
return {
|
|
543
|
+
...analysis,
|
|
544
|
+
direction: signalDirection,
|
|
545
|
+
quality,
|
|
546
|
+
needRetest: false,
|
|
547
|
+
retestPrice: null,
|
|
548
|
+
takeProfitPrice: analysis.takeProfitPrice ?? signal.prices?.takeProfitPrice ?? null,
|
|
549
|
+
stopLossPrice: analysis.stopLossPrice ?? signal.prices?.stopLossPrice ?? null
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const retestPrice = trendlineContext.currentLinePrice ?? analysis.retestPrice ?? null;
|
|
553
|
+
const qualityReason = mergeShortText(
|
|
554
|
+
getDeterministicTrendlineQualityReason(trendlineContext),
|
|
555
|
+
"TrendLine guardrail: \u0432\u0445\u043E\u0434 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.",
|
|
556
|
+
400
|
|
557
|
+
);
|
|
558
|
+
const triggerInvalidation = mergeShortText(
|
|
559
|
+
trendlineContext.hardBlockReasons.length > 0 ? `\u0416\u0434\u0430\u0442\u044C \u0447\u0438\u0441\u0442\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439/\u0440\u0435\u0442\u0435\u0441\u0442 \u043B\u0438\u043D\u0438\u0438 \u0438 \u0443\u0431\u0440\u0430\u0442\u044C \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B: ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "\u0416\u0434\u0430\u0442\u044C \u0431\u043E\u043B\u0435\u0435 \u0441\u0438\u043B\u044C\u043D\u044B\u0439 breakout/follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442 \u043B\u0438\u043D\u0438\u0438 \u0441 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435\u043C \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC.",
|
|
560
|
+
"\u0416\u0434\u0430\u0442\u044C \u0447\u0438\u0441\u0442\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439/\u0440\u0435\u0442\u0435\u0441\u0442 \u043B\u0438\u043D\u0438\u0438 \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC.",
|
|
561
|
+
400
|
|
562
|
+
);
|
|
563
|
+
const comment = mergeShortText(
|
|
564
|
+
trendlineContext.hardBlockReasons.length > 0 ? `TrendLine guardrail \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B \u0432\u0445\u043E\u0434: ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "TrendLine deterministic quality \u043E\u043F\u0443\u0441\u0442\u0438\u043B \u0432\u0445\u043E\u0434 \u0432 watch/reject \u0434\u043E \u043F\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u044F \u0431\u043E\u043B\u0435\u0435 \u0441\u0438\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.",
|
|
565
|
+
"TrendLine guardrail \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B \u0432\u0445\u043E\u0434 \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.",
|
|
566
|
+
1024
|
|
567
|
+
);
|
|
568
|
+
return {
|
|
569
|
+
...analysis,
|
|
570
|
+
direction: null,
|
|
571
|
+
quality,
|
|
572
|
+
needRetest: true,
|
|
573
|
+
retestPrice,
|
|
574
|
+
takeProfitPrice: null,
|
|
575
|
+
stopLossPrice: null,
|
|
576
|
+
setup: mergeShortText(
|
|
577
|
+
analysis.setup ?? "",
|
|
578
|
+
"\u0421\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F/\u0440\u0435\u0442\u0435\u0441\u0442\u0430 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u0434\u043B\u044F \u0432\u0445\u043E\u0434\u0430.",
|
|
579
|
+
400
|
|
580
|
+
),
|
|
581
|
+
retestPlan: mergeShortText(
|
|
582
|
+
analysis.retestPlan ?? "",
|
|
583
|
+
"\u0416\u0434\u0430\u0442\u044C \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043A \u043B\u0438\u043D\u0438\u0438 \u0438 \u0440\u0435\u0430\u043A\u0446\u0438\u044E \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0441\u0434\u0435\u043B\u043A\u0438 \u043F\u0435\u0440\u0435\u0434 \u043D\u043E\u0432\u044B\u043C \u0432\u0445\u043E\u0434\u043E\u043C.",
|
|
584
|
+
400
|
|
585
|
+
),
|
|
586
|
+
qualityReason,
|
|
587
|
+
triggerInvalidation,
|
|
588
|
+
comment
|
|
589
|
+
};
|
|
590
|
+
},
|
|
591
|
+
buildSystemPromptAddon: () => `
|
|
592
|
+
${TRENDLINE_CONTEXT_PROMPT}
|
|
593
|
+
${TRENDLINE_PAYLOAD_PROMPT}
|
|
594
|
+
`,
|
|
595
|
+
buildHumanPromptAddon: ({ payload }) => {
|
|
596
|
+
const additional = payload.additionalIndicators;
|
|
597
|
+
const trendlineContext = additional?.trendlineContext;
|
|
598
|
+
if (!trendlineContext) {
|
|
599
|
+
return "";
|
|
600
|
+
}
|
|
601
|
+
return `
|
|
602
|
+
|
|
603
|
+
\u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 TrendLine:
|
|
604
|
+
- trendline.mode=${trendlineContext.mode ?? "n/a"}
|
|
605
|
+
- trendline.touches=${formatPromptNumber(trendlineContext.touches, 0)}
|
|
606
|
+
- trendline.distance=${formatPromptNumber(trendlineContext.distance, 0)}
|
|
607
|
+
- trendline.currentLinePrice=${formatPromptNumber(trendlineContext.currentLinePrice, 6)}
|
|
608
|
+
- trendline.currentPrice=${formatPromptNumber(trendlineContext.currentPrice, 6)}
|
|
609
|
+
- trendline.priceVsLinePct=${formatPromptNumber(trendlineContext.priceVsLinePct, 3)}%
|
|
610
|
+
- trendline.priceVsLineSide=${trendlineContext.priceVsLineSide ?? "n/a"}
|
|
611
|
+
- trendline.clearBreak=${String(trendlineContext.clearBreak)}
|
|
612
|
+
- trendline.nearLineNoise=${String(trendlineContext.nearLineNoise)}
|
|
613
|
+
- trendline.atrPct=${formatPromptNumber(trendlineContext.atrPct, 3)}%
|
|
614
|
+
- trendline.breakVsAtrRatio=${formatPromptNumber(trendlineContext.breakVsAtrRatio, 3)}
|
|
615
|
+
- trendline.aggressivePreBreakPressure=${String(trendlineContext.aggressivePreBreakPressure)}
|
|
616
|
+
- trendline.strongNearBreakPressure=${String(trendlineContext.strongNearBreakPressure)}
|
|
617
|
+
- trendline.weakCleanBreak=${String(trendlineContext.weakCleanBreak)}
|
|
618
|
+
- trendline.compressedCleanBreak=${String(trendlineContext.compressedCleanBreak)}
|
|
619
|
+
- trendline.weakBtcLedBreak=${String(trendlineContext.weakBtcLedBreak)}
|
|
620
|
+
- trendline.weakLongFarBreak=${String(trendlineContext.weakLongFarBreak)}
|
|
621
|
+
- trendline.entryTiming=${trendlineContext.entryTiming ?? "n/a"}
|
|
622
|
+
- trendline.deterministicQuality=${String(trendlineContext.deterministicQuality)}
|
|
623
|
+
- trendline.maxAllowedQuality=${String(trendlineContext.maxAllowedQuality)}
|
|
624
|
+
- trendline.approvalAllowedNow=${String(trendlineContext.approvalAllowedNow)}
|
|
625
|
+
- trendline.hardBlockReasons=${JSON.stringify(trendlineContext.hardBlockReasons)}
|
|
626
|
+
- coin.maFastLast=${formatPromptNumber(trendlineContext.coinMaFast, 6)}
|
|
627
|
+
- coin.maSlowLast=${formatPromptNumber(trendlineContext.coinMaSlow, 6)}
|
|
628
|
+
- coin.maBias=${trendlineContext.coinMaBias ?? "n/a"}
|
|
629
|
+
- coin.maSpreadPct=${formatPromptNumber(trendlineContext.coinMaSpreadPct, 3)}%
|
|
630
|
+
- coin.biasAligned=${String(trendlineContext.coinBiasAligned)}
|
|
631
|
+
- btc.maFastLast=${formatPromptNumber(trendlineContext.btcMaFast, 2)}
|
|
632
|
+
- btc.maSlowLast=${formatPromptNumber(trendlineContext.btcMaSlow, 2)}
|
|
633
|
+
- btc.maBias=${trendlineContext.btcMaBias ?? "n/a"}
|
|
634
|
+
- btc.maSpreadPct=${formatPromptNumber(trendlineContext.btcMaSpreadPct, 3)}%
|
|
635
|
+
- btc.biasAligned=${String(trendlineContext.btcBiasAligned)}
|
|
636
|
+
|
|
637
|
+
\u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F TrendLine:
|
|
638
|
+
- SHORT \u043E\u0442 \u043B\u0438\u043D\u0438\u0438 lows \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u044F\u0432\u043D\u044B\u043C \u0443\u0445\u043E\u0434\u043E\u043C \u043D\u0438\u0436\u0435 \u043B\u0438\u043D\u0438\u0438 \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442\u043E\u043C \u0441\u043D\u0438\u0437\u0443 \u0441 \u043E\u0442\u0431\u043E\u0435\u043C.
|
|
639
|
+
- LONG \u043E\u0442 \u043B\u0438\u043D\u0438\u0438 highs \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u044F\u0432\u043D\u044B\u043C \u0443\u0445\u043E\u0434\u043E\u043C \u0432\u044B\u0448\u0435 \u043B\u0438\u043D\u0438\u0438 \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442\u043E\u043C \u0441\u0432\u0435\u0440\u0445\u0443 \u0441 \u043E\u0442\u0431\u043E\u0435\u043C.
|
|
640
|
+
- \u0415\u0441\u043B\u0438 trendline.nearLineNoise=true \u0438\u043B\u0438 biasAligned=false, \u043B\u0443\u0447\u0448\u0435 \u0432\u0435\u0440\u043D\u0443\u0442\u044C direction=null \u0438 quality 1-3, \u0447\u0435\u043C \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C \u0431\u0435\u0437 \u0437\u0430\u043F\u0430\u0441\u0430.
|
|
641
|
+
- \u0415\u0441\u043B\u0438 trendline.weakCleanBreak=true, \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C, \u043D\u043E \u043E\u043D \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u044B\u0439 \u043F\u043E displacement: \u043D\u0443\u0436\u0435\u043D follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442, \u0430 \u043D\u0435 quality 4-5.
|
|
642
|
+
- \u0415\u0441\u043B\u0438 trendline.compressedCleanBreak=true, \u043F\u0440\u043E\u0431\u043E\u0439 \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u043E \u0435\u0441\u0442\u044C, \u043D\u043E \u043B\u0438\u043D\u0438\u044F \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043A\u043E\u0440\u043E\u0442\u043A\u0430\u044F \u0438 \u0441\u0436\u0430\u0442\u0430\u044F \u043F\u043E\u0441\u043B\u0435 \u0441\u0435\u0440\u0438\u0438 \u0431\u043B\u0438\u0437\u043A\u0438\u0445 \u043A\u0430\u0441\u0430\u043D\u0438\u0439: \u043E\u0431\u044B\u0447\u043D\u043E \u0437\u0434\u0435\u0441\u044C \u043D\u0443\u0436\u0435\u043D follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
|
|
643
|
+
- \u0415\u0441\u043B\u0438 trendline.weakBtcLedBreak=true, \u0442\u0440\u0430\u043A\u0442\u0443\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u043C\u0435\u043B\u043A\u0438\u0439 \u043F\u0440\u043E\u0431\u043E\u0439, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0441\u0438\u043B\u044C\u043D\u0435\u0435 \u0442\u044F\u043D\u0435\u0442 BTC, \u0447\u0435\u043C \u0441\u0430\u043C\u0430 \u043C\u043E\u043D\u0435\u0442\u0430: \u0437\u0434\u0435\u0441\u044C \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u0442\u0435\u0441\u0442 \u0438 quality 1-3.
|
|
644
|
+
- \u0415\u0441\u043B\u0438 clearBreak=false \u0438\u043B\u0438 \u043B\u044E\u0431\u043E\u0439 alignment=false, \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0439 quality \u0432\u044B\u0448\u0435 3.
|
|
645
|
+
- \u0415\u0441\u043B\u0438 trendline.aggressivePreBreakPressure=true, \u043C\u043E\u0436\u043D\u043E \u0440\u0430\u0441\u0441\u043C\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044C \u0440\u0430\u043D\u043D\u0438\u0439 SHORT \u0434\u043E \u044F\u0432\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F, \u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u043A \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: quality \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C 4 \u0438 \u044F\u0432\u043D\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435, \u0447\u0442\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E\u043A\u0430 \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043D\u043E\u0435.
|
|
646
|
+
- \u0415\u0441\u043B\u0438 trendline.strongNearBreakPressure=true, \u043C\u043E\u0436\u043D\u043E \u0440\u0430\u0441\u0441\u043C\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044C \u0440\u0430\u043D\u043D\u0438\u0439 SHORT \u043F\u0440\u0438 \u0441\u0438\u043B\u044C\u043D\u043E\u043C \u0434\u0430\u0432\u043B\u0435\u043D\u0438\u0438 \u0443\u0436\u0435 \u043F\u043E \u043D\u0443\u0436\u043D\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u043B\u0438\u043D\u0438\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043F\u0440\u043E\u0431\u043E\u0439 \u0435\u0449\u0435 \u043D\u0435 \u0434\u043E\u0442\u044F\u0433\u0438\u0432\u0430\u0435\u0442 \u0434\u043E clearBreak-\u043F\u043E\u0440\u043E\u0433\u0430: quality \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C 4.
|
|
647
|
+
- \u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F \u0434\u0435\u0442\u0435\u0440\u043C\u0438\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u0443\u0435\u0442 \u0438\u0442\u043E\u0433\u043E\u0432\u044B\u0439 quality \u0434\u043E trendline.deterministicQuality; \u0442\u0432\u043E\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u043E\u0431\u044A\u044F\u0441\u043D\u0438\u0442\u044C \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0432 \u044D\u0442\u0438\u0445 \u0440\u0430\u043C\u043A\u0430\u0445, \u0430 \u043D\u0435 \u0441\u043F\u043E\u0440\u0438\u0442\u044C \u0441 tier.
|
|
648
|
+
- \u0415\u0441\u043B\u0438 trendline.approvalAllowedNow=false, \u043D\u0435 \u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441: \u043E\u0431\u044A\u044F\u0441\u043D\u044F\u0439, \u0447\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
|
|
649
|
+
`;
|
|
650
|
+
},
|
|
651
|
+
mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
|
|
652
|
+
config2
|
|
653
|
+
)
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// src/TrendLine/adapters/ml.ts
|
|
657
|
+
import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
658
|
+
var toTrendLineMlStrategyConfig = (input) => {
|
|
659
|
+
if (!input) return void 0;
|
|
660
|
+
return {
|
|
661
|
+
...input,
|
|
662
|
+
TRENDLINE_CONFIG: input.TRENDLINE_CONFIG ?? input.TRENDLINE ?? {}
|
|
663
|
+
};
|
|
664
|
+
};
|
|
665
|
+
var trendLineMlAdapter = {
|
|
666
|
+
normalizeSignal: (signal) => {
|
|
667
|
+
const nextSignal = {
|
|
668
|
+
...signal,
|
|
669
|
+
indicators: {
|
|
670
|
+
...signal.indicators ?? {}
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
const additional = signal.additionalIndicators ?? {};
|
|
674
|
+
if (nextSignal.indicators.touches == null && additional.touches != null) {
|
|
675
|
+
nextSignal.indicators.touches = additional.touches;
|
|
676
|
+
}
|
|
677
|
+
if (nextSignal.indicators.distance == null && additional.distance != null) {
|
|
678
|
+
nextSignal.indicators.distance = additional.distance;
|
|
679
|
+
}
|
|
680
|
+
return nextSignal;
|
|
681
|
+
},
|
|
682
|
+
normalizeStrategyConfig: (strategyConfig) => {
|
|
683
|
+
return toTrendLineMlStrategyConfig(strategyConfig);
|
|
684
|
+
},
|
|
685
|
+
mapEntryRuntimeFromConfig: (config2) => mapMlRuntimeFromConfig(config2, {
|
|
686
|
+
strategyConfig: toTrendLineMlStrategyConfig(
|
|
687
|
+
config2
|
|
688
|
+
)
|
|
689
|
+
})
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
// src/TrendLine/hooks.ts
|
|
693
|
+
import { createCloseOppositeBeforePlaceOrderHook } from "@tradejs/node/strategies";
|
|
694
|
+
var trendLineBeforePlaceOrderHook = createCloseOppositeBeforePlaceOrderHook({
|
|
695
|
+
isEnabled: (config2) => Boolean(config2.CLOSE_OPPOSITE_POSITIONS)
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// src/TrendLine/manifest.ts
|
|
699
|
+
var trendLineManifest = {
|
|
700
|
+
name: "TrendLine",
|
|
701
|
+
hooks: {
|
|
702
|
+
beforePlaceOrder: trendLineBeforePlaceOrderHook
|
|
703
|
+
},
|
|
704
|
+
aiAdapter: trendLineAiAdapter,
|
|
705
|
+
mlAdapter: trendLineMlAdapter
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/TrendLine/config.ts
|
|
709
|
+
var config = {
|
|
710
|
+
ENV: "BACKTEST",
|
|
711
|
+
INTERVAL: "15",
|
|
712
|
+
MAKE_ORDERS: true,
|
|
713
|
+
CLOSE_OPPOSITE_POSITIONS: false,
|
|
714
|
+
BACKTEST_PRICE_MODE: "mid",
|
|
715
|
+
AI_ENABLED: false,
|
|
716
|
+
ML_ENABLED: false,
|
|
717
|
+
ML_THRESHOLD: 0.1,
|
|
718
|
+
MIN_AI_QUALITY: 3,
|
|
719
|
+
FEE_PERCENT: 5e-3,
|
|
720
|
+
MAX_LOSS_VALUE: 10,
|
|
721
|
+
MA_FAST: 14,
|
|
722
|
+
MA_MEDIUM: 49,
|
|
723
|
+
MA_SLOW: 50,
|
|
724
|
+
OBV_SMA: 10,
|
|
725
|
+
ATR: 14,
|
|
726
|
+
ATR_PCT_SHORT: 7,
|
|
727
|
+
ATR_PCT_LONG: 30,
|
|
728
|
+
BB: 20,
|
|
729
|
+
BB_STD: 2,
|
|
730
|
+
MACD_FAST: 12,
|
|
731
|
+
MACD_SLOW: 26,
|
|
732
|
+
MACD_SIGNAL: 9,
|
|
733
|
+
LEVEL_LOOKBACK: 20,
|
|
734
|
+
LEVEL_DELAY: 2,
|
|
735
|
+
TRENDLINE: {
|
|
736
|
+
minTouches: 4,
|
|
737
|
+
offset: 3,
|
|
738
|
+
epsilon: 3e-3,
|
|
739
|
+
epsilonOffset: 4e-3
|
|
740
|
+
},
|
|
741
|
+
HIGHS: {
|
|
742
|
+
enable: true,
|
|
743
|
+
direction: "LONG",
|
|
744
|
+
TP: 4,
|
|
745
|
+
SL: 1.3,
|
|
746
|
+
minRiskRatio: 2
|
|
747
|
+
},
|
|
748
|
+
LOWS: {
|
|
749
|
+
enable: true,
|
|
750
|
+
direction: "SHORT",
|
|
751
|
+
TP: 4,
|
|
752
|
+
SL: 1.3,
|
|
753
|
+
minRiskRatio: 2
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
export {
|
|
758
|
+
buildTrendlineStructuralContext,
|
|
759
|
+
buildTrendlineTimingContext,
|
|
760
|
+
trendLineManifest,
|
|
761
|
+
config
|
|
762
|
+
};
|