@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,687 @@
|
|
|
1
|
+
// src/ReverseTrendLine/adapters/ai.ts
|
|
2
|
+
import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
3
|
+
|
|
4
|
+
// src/ReverseTrendLine/guardrails.ts
|
|
5
|
+
var REVERSE_TRENDLINE_NEAR_LINE_PCT = 0.45;
|
|
6
|
+
var REVERSE_TRENDLINE_FAILED_BOUNCE_PCT = 0.35;
|
|
7
|
+
var REVERSE_TRENDLINE_TIMING_WINDOW = 6;
|
|
8
|
+
var MIN_REJECTION_WICK_PCT = 0.12;
|
|
9
|
+
var MIN_REJECTION_STRENGTH_PCT = 0.08;
|
|
10
|
+
var FOLLOW_THROUGH_STRENGTH_PCT = 0.18;
|
|
11
|
+
var toFiniteNumberOrNull = (value) => {
|
|
12
|
+
const num = Number(value);
|
|
13
|
+
return Number.isFinite(num) ? num : null;
|
|
14
|
+
};
|
|
15
|
+
var getLastFiniteNumber = (value) => {
|
|
16
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return toFiniteNumberOrNull(value[value.length - 1]);
|
|
20
|
+
};
|
|
21
|
+
var getBias = (fast, slow) => {
|
|
22
|
+
if (fast == null || slow == null) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (fast > slow) {
|
|
26
|
+
return "bullish";
|
|
27
|
+
}
|
|
28
|
+
if (fast < slow) {
|
|
29
|
+
return "bearish";
|
|
30
|
+
}
|
|
31
|
+
return "flat";
|
|
32
|
+
};
|
|
33
|
+
var getSpreadPct = (fast, slow) => {
|
|
34
|
+
if (fast == null || slow == null || slow === 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return (fast - slow) / slow * 100;
|
|
38
|
+
};
|
|
39
|
+
var getTrendLineFromPayload = (signal) => signal.figures?.trendLine ?? signal.additionalIndicators?.trendLine ?? null;
|
|
40
|
+
var deriveDirectionFromMode = (mode) => {
|
|
41
|
+
if (mode === "lows") {
|
|
42
|
+
return "LONG";
|
|
43
|
+
}
|
|
44
|
+
if (mode === "highs") {
|
|
45
|
+
return "SHORT";
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
var getSortedTrendLinePoints = (trendLine) => {
|
|
50
|
+
const rawPoints = Array.isArray(trendLine?.points) ? trendLine.points : [];
|
|
51
|
+
return rawPoints.map((point) => {
|
|
52
|
+
if (!point || typeof point !== "object") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const typedPoint = point;
|
|
56
|
+
const timestamp = toFiniteNumberOrNull(typedPoint.timestamp);
|
|
57
|
+
const value = toFiniteNumberOrNull(typedPoint.value);
|
|
58
|
+
if (timestamp == null || value == null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return { timestamp, value };
|
|
62
|
+
}).filter(Boolean).sort((left, right) => left.timestamp - right.timestamp);
|
|
63
|
+
};
|
|
64
|
+
var buildTrendLineEvaluator = (trendLine) => {
|
|
65
|
+
const points = getSortedTrendLinePoints(trendLine);
|
|
66
|
+
if (points.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const firstPoint = points[0];
|
|
70
|
+
const lastPoint = points[points.length - 1];
|
|
71
|
+
const deltaTime = lastPoint.timestamp - firstPoint.timestamp;
|
|
72
|
+
if (deltaTime === 0) {
|
|
73
|
+
return {
|
|
74
|
+
firstPoint,
|
|
75
|
+
lastPoint,
|
|
76
|
+
evaluate: (_timestamp) => lastPoint.value
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const slope = (lastPoint.value - firstPoint.value) / deltaTime;
|
|
80
|
+
return {
|
|
81
|
+
firstPoint,
|
|
82
|
+
lastPoint,
|
|
83
|
+
evaluate: (timestamp) => firstPoint.value + slope * (timestamp - firstPoint.timestamp)
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
var getCurrentCandle = (signal) => {
|
|
87
|
+
const candle = signal.additionalIndicators?.currentCandle;
|
|
88
|
+
return candle && typeof candle === "object" ? candle : null;
|
|
89
|
+
};
|
|
90
|
+
var getLineTouched = ({
|
|
91
|
+
low,
|
|
92
|
+
high,
|
|
93
|
+
linePrice
|
|
94
|
+
}) => {
|
|
95
|
+
if (low == null || high == null || linePrice == null) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return low <= linePrice && high >= linePrice;
|
|
99
|
+
};
|
|
100
|
+
var getCloseOnBounceSide = ({
|
|
101
|
+
direction,
|
|
102
|
+
priceVsLinePct
|
|
103
|
+
}) => {
|
|
104
|
+
if (direction == null || priceVsLinePct == null) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return direction === "LONG" ? priceVsLinePct >= 0 : priceVsLinePct <= 0;
|
|
108
|
+
};
|
|
109
|
+
var getFailedBounceBreak = ({
|
|
110
|
+
direction,
|
|
111
|
+
priceVsLinePct
|
|
112
|
+
}) => {
|
|
113
|
+
if (direction == null || priceVsLinePct == null) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return direction === "LONG" ? priceVsLinePct <= -REVERSE_TRENDLINE_FAILED_BOUNCE_PCT : priceVsLinePct >= REVERSE_TRENDLINE_FAILED_BOUNCE_PCT;
|
|
117
|
+
};
|
|
118
|
+
var getBodyAligned = ({
|
|
119
|
+
direction,
|
|
120
|
+
open,
|
|
121
|
+
close
|
|
122
|
+
}) => {
|
|
123
|
+
if (direction == null || open == null || close == null) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return direction === "LONG" ? close >= open : close <= open;
|
|
127
|
+
};
|
|
128
|
+
var getRejectionWickPct = ({
|
|
129
|
+
direction,
|
|
130
|
+
open,
|
|
131
|
+
close,
|
|
132
|
+
high,
|
|
133
|
+
low
|
|
134
|
+
}) => {
|
|
135
|
+
if (direction == null || open == null || close == null || high == null || low == null || close <= 0) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const lowerWick = Math.max(0, Math.min(open, close) - low);
|
|
139
|
+
const upperWick = Math.max(0, high - Math.max(open, close));
|
|
140
|
+
return direction === "LONG" ? lowerWick / close * 100 : upperWick / close * 100;
|
|
141
|
+
};
|
|
142
|
+
var getRejectionStrengthPct = ({
|
|
143
|
+
direction,
|
|
144
|
+
close,
|
|
145
|
+
linePrice
|
|
146
|
+
}) => {
|
|
147
|
+
if (direction == null || close == null || linePrice == null || linePrice === 0) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
if (direction === "LONG") {
|
|
151
|
+
return close >= linePrice ? (close - linePrice) / linePrice * 100 : 0;
|
|
152
|
+
}
|
|
153
|
+
return close <= linePrice ? (linePrice - close) / linePrice * 100 : 0;
|
|
154
|
+
};
|
|
155
|
+
var getRejectionBar = ({
|
|
156
|
+
direction,
|
|
157
|
+
lineTouched,
|
|
158
|
+
closeOnBounceSide,
|
|
159
|
+
bodyAligned,
|
|
160
|
+
rejectionWickPct,
|
|
161
|
+
rejectionStrengthPct
|
|
162
|
+
}) => {
|
|
163
|
+
if (direction == null) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return lineTouched && closeOnBounceSide === true && bodyAligned === true && (rejectionWickPct ?? 0) >= MIN_REJECTION_WICK_PCT && (rejectionStrengthPct ?? 0) >= MIN_REJECTION_STRENGTH_PCT;
|
|
167
|
+
};
|
|
168
|
+
var buildReverseTrendlineStructuralContext = (signal) => {
|
|
169
|
+
const trendLine = getTrendLineFromPayload(signal);
|
|
170
|
+
const evaluator = buildTrendLineEvaluator(trendLine);
|
|
171
|
+
const currentPrice = toFiniteNumberOrNull(signal.prices?.currentPrice);
|
|
172
|
+
const currentCandle = getCurrentCandle(signal);
|
|
173
|
+
const currentTimestamp = toFiniteNumberOrNull(currentCandle?.timestamp);
|
|
174
|
+
const currentOpen = toFiniteNumberOrNull(currentCandle?.open);
|
|
175
|
+
const currentClose = toFiniteNumberOrNull(currentCandle?.close) ?? currentPrice;
|
|
176
|
+
const currentHigh = toFiniteNumberOrNull(currentCandle?.high);
|
|
177
|
+
const currentLow = toFiniteNumberOrNull(currentCandle?.low);
|
|
178
|
+
const signalDirection = signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : deriveDirectionFromMode(trendLine?.mode);
|
|
179
|
+
const currentLinePrice = currentTimestamp != null && evaluator ? evaluator.evaluate(currentTimestamp) : evaluator?.lastPoint.value ?? null;
|
|
180
|
+
const priceVsLinePct = currentClose != null && currentLinePrice != null && currentLinePrice !== 0 ? (currentClose - currentLinePrice) / currentLinePrice * 100 : null;
|
|
181
|
+
const priceVsLinePctAbs = priceVsLinePct == null ? null : Math.abs(priceVsLinePct);
|
|
182
|
+
const priceVsLineSide = priceVsLinePct == null ? null : priceVsLinePct > 0 ? "above" : priceVsLinePct < 0 ? "below" : "at";
|
|
183
|
+
const nearLine = priceVsLinePctAbs == null ? null : priceVsLinePctAbs <= REVERSE_TRENDLINE_NEAR_LINE_PCT;
|
|
184
|
+
const lineTouchedNow = getLineTouched({
|
|
185
|
+
low: currentLow,
|
|
186
|
+
high: currentHigh,
|
|
187
|
+
linePrice: currentLinePrice
|
|
188
|
+
});
|
|
189
|
+
const closeOnBounceSide = getCloseOnBounceSide({
|
|
190
|
+
direction: signalDirection,
|
|
191
|
+
priceVsLinePct
|
|
192
|
+
});
|
|
193
|
+
const failedBounceBreak = getFailedBounceBreak({
|
|
194
|
+
direction: signalDirection,
|
|
195
|
+
priceVsLinePct
|
|
196
|
+
});
|
|
197
|
+
const bodyAligned = getBodyAligned({
|
|
198
|
+
direction: signalDirection,
|
|
199
|
+
open: currentOpen,
|
|
200
|
+
close: currentClose
|
|
201
|
+
});
|
|
202
|
+
const rejectionWickPct = getRejectionWickPct({
|
|
203
|
+
direction: signalDirection,
|
|
204
|
+
open: currentOpen,
|
|
205
|
+
close: currentClose,
|
|
206
|
+
high: currentHigh,
|
|
207
|
+
low: currentLow
|
|
208
|
+
});
|
|
209
|
+
const rejectionStrengthPct = getRejectionStrengthPct({
|
|
210
|
+
direction: signalDirection,
|
|
211
|
+
close: currentClose,
|
|
212
|
+
linePrice: currentLinePrice
|
|
213
|
+
});
|
|
214
|
+
const rejectionBarNow = getRejectionBar({
|
|
215
|
+
direction: signalDirection,
|
|
216
|
+
lineTouched: lineTouchedNow,
|
|
217
|
+
closeOnBounceSide,
|
|
218
|
+
bodyAligned,
|
|
219
|
+
rejectionWickPct,
|
|
220
|
+
rejectionStrengthPct
|
|
221
|
+
});
|
|
222
|
+
const touchesTotal = toFiniteNumberOrNull(
|
|
223
|
+
signal.additionalIndicators?.touches
|
|
224
|
+
);
|
|
225
|
+
const distance = toFiniteNumberOrNull(signal.additionalIndicators?.distance);
|
|
226
|
+
const touches = touchesTotal != null ? touchesTotal : Array.isArray(trendLine?.touches) ? trendLine.touches.length : null;
|
|
227
|
+
const atrPct = getLastFiniteNumber(signal.indicators?.atrPct);
|
|
228
|
+
const breakVsAtrRatio = rejectionStrengthPct != null && atrPct != null && atrPct > 0 ? rejectionStrengthPct / atrPct : null;
|
|
229
|
+
const coinMaFast = getLastFiniteNumber(signal.indicators?.maFast);
|
|
230
|
+
const coinMaSlow = getLastFiniteNumber(signal.indicators?.maSlow);
|
|
231
|
+
const coinMaBias = getBias(coinMaFast, coinMaSlow);
|
|
232
|
+
const coinMaSpreadPct = getSpreadPct(coinMaFast, coinMaSlow);
|
|
233
|
+
const coinBiasAligned = signalDirection == null || coinMaBias == null ? null : signalDirection === "LONG" ? coinMaBias === "bullish" : coinMaBias === "bearish";
|
|
234
|
+
const btcMaFast = getLastFiniteNumber(signal.indicators?.btcMaFast);
|
|
235
|
+
const btcMaSlow = getLastFiniteNumber(signal.indicators?.btcMaSlow);
|
|
236
|
+
const btcMaBias = getBias(btcMaFast, btcMaSlow);
|
|
237
|
+
const btcMaSpreadPct = getSpreadPct(btcMaFast, btcMaSlow);
|
|
238
|
+
const btcBiasAligned = signalDirection == null || btcMaBias == null ? null : signalDirection === "LONG" ? btcMaBias === "bullish" : btcMaBias === "bearish";
|
|
239
|
+
const structuralHardBlockReasons = [];
|
|
240
|
+
if (failedBounceBreak === true) {
|
|
241
|
+
structuralHardBlockReasons.push("failed_bounce_break");
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
signalDirection,
|
|
245
|
+
mode: typeof trendLine?.mode === "string" ? trendLine.mode : null,
|
|
246
|
+
currentPrice,
|
|
247
|
+
currentLinePrice,
|
|
248
|
+
priceVsLinePct,
|
|
249
|
+
priceVsLinePctAbs,
|
|
250
|
+
priceVsLineSide,
|
|
251
|
+
nearLine,
|
|
252
|
+
lineTouchedNow,
|
|
253
|
+
closeOnBounceSide,
|
|
254
|
+
failedBounceBreak,
|
|
255
|
+
bodyAligned,
|
|
256
|
+
rejectionWickPct,
|
|
257
|
+
rejectionStrengthPct,
|
|
258
|
+
rejectionBarNow,
|
|
259
|
+
touches,
|
|
260
|
+
distance,
|
|
261
|
+
atrPct,
|
|
262
|
+
breakVsAtrRatio,
|
|
263
|
+
coinMaFast,
|
|
264
|
+
coinMaSlow,
|
|
265
|
+
coinMaBias,
|
|
266
|
+
coinMaSpreadPct,
|
|
267
|
+
coinBiasAligned,
|
|
268
|
+
btcMaFast,
|
|
269
|
+
btcMaSlow,
|
|
270
|
+
btcMaBias,
|
|
271
|
+
btcMaSpreadPct,
|
|
272
|
+
btcBiasAligned,
|
|
273
|
+
structuralHardBlockReasons
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
var buildReverseTrendlineTimingContext = ({
|
|
277
|
+
signal,
|
|
278
|
+
candles,
|
|
279
|
+
structuralContext
|
|
280
|
+
}) => {
|
|
281
|
+
const structural = structuralContext ?? buildReverseTrendlineStructuralContext(signal);
|
|
282
|
+
const trendLine = getTrendLineFromPayload(signal);
|
|
283
|
+
const evaluator = buildTrendLineEvaluator(trendLine);
|
|
284
|
+
const timingCandles = Array.isArray(candles) ? candles.slice(-REVERSE_TRENDLINE_TIMING_WINDOW) : [];
|
|
285
|
+
const sortedCandles = [...timingCandles].sort(
|
|
286
|
+
(left, right) => Number(left?.timestamp ?? 0) - Number(right?.timestamp ?? 0)
|
|
287
|
+
);
|
|
288
|
+
const recentSamples = evaluator ? sortedCandles.map((candle) => {
|
|
289
|
+
const timestamp = toFiniteNumberOrNull(candle.timestamp);
|
|
290
|
+
const open = toFiniteNumberOrNull(candle.open);
|
|
291
|
+
const close = toFiniteNumberOrNull(candle.close);
|
|
292
|
+
const high = toFiniteNumberOrNull(candle.high);
|
|
293
|
+
const low = toFiniteNumberOrNull(candle.low);
|
|
294
|
+
if (timestamp == null || open == null || close == null || high == null || low == null) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
const linePrice = evaluator.evaluate(timestamp);
|
|
298
|
+
const priceVsLinePct = linePrice !== 0 ? (close - linePrice) / linePrice * 100 : null;
|
|
299
|
+
const closeOnBounceSide = getCloseOnBounceSide({
|
|
300
|
+
direction: structural.signalDirection,
|
|
301
|
+
priceVsLinePct
|
|
302
|
+
});
|
|
303
|
+
const failedBounceBreak = getFailedBounceBreak({
|
|
304
|
+
direction: structural.signalDirection,
|
|
305
|
+
priceVsLinePct
|
|
306
|
+
});
|
|
307
|
+
const lineTouched = getLineTouched({
|
|
308
|
+
low,
|
|
309
|
+
high,
|
|
310
|
+
linePrice
|
|
311
|
+
});
|
|
312
|
+
const bodyAligned = getBodyAligned({
|
|
313
|
+
direction: structural.signalDirection,
|
|
314
|
+
open,
|
|
315
|
+
close
|
|
316
|
+
});
|
|
317
|
+
const rejectionWickPct = getRejectionWickPct({
|
|
318
|
+
direction: structural.signalDirection,
|
|
319
|
+
open,
|
|
320
|
+
close,
|
|
321
|
+
high,
|
|
322
|
+
low
|
|
323
|
+
});
|
|
324
|
+
const rejectionStrengthPct = getRejectionStrengthPct({
|
|
325
|
+
direction: structural.signalDirection,
|
|
326
|
+
close,
|
|
327
|
+
linePrice
|
|
328
|
+
});
|
|
329
|
+
const rejectionBar = getRejectionBar({
|
|
330
|
+
direction: structural.signalDirection,
|
|
331
|
+
lineTouched,
|
|
332
|
+
closeOnBounceSide,
|
|
333
|
+
bodyAligned,
|
|
334
|
+
rejectionWickPct,
|
|
335
|
+
rejectionStrengthPct
|
|
336
|
+
});
|
|
337
|
+
return {
|
|
338
|
+
timestamp,
|
|
339
|
+
priceVsLinePct,
|
|
340
|
+
lineTouched,
|
|
341
|
+
closeOnBounceSide,
|
|
342
|
+
failedBounceBreak,
|
|
343
|
+
rejectionWickPct,
|
|
344
|
+
rejectionStrengthPct,
|
|
345
|
+
rejectionBar
|
|
346
|
+
};
|
|
347
|
+
}).filter(Boolean) : [];
|
|
348
|
+
const currentIndex = recentSamples.length - 1;
|
|
349
|
+
const lastSample = currentIndex >= 0 ? recentSamples[currentIndex] : null;
|
|
350
|
+
const prevSample = currentIndex > 0 ? recentSamples[currentIndex - 1] : null;
|
|
351
|
+
let latestRejectionIndex = null;
|
|
352
|
+
for (let index = 0; index < recentSamples.length; index += 1) {
|
|
353
|
+
if (recentSamples[index].rejectionBar === true) {
|
|
354
|
+
latestRejectionIndex = index;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const barsSinceRejection = latestRejectionIndex != null ? currentIndex - latestRejectionIndex : null;
|
|
358
|
+
const rejectionFresh = barsSinceRejection != null && barsSinceRejection >= 0 && barsSinceRejection <= 1;
|
|
359
|
+
const followThroughReady = latestRejectionIndex != null && latestRejectionIndex === currentIndex - 1 && lastSample?.closeOnBounceSide === true && lastSample.failedBounceBreak !== true && lastSample.lineTouched === false && (lastSample.rejectionStrengthPct ?? 0) >= FOLLOW_THROUGH_STRENGTH_PCT;
|
|
360
|
+
const staleReaction = latestRejectionIndex != null && barsSinceRejection != null && barsSinceRejection > 1 && lastSample?.failedBounceBreak !== true;
|
|
361
|
+
let entryTiming = "unknown";
|
|
362
|
+
if (lastSample?.failedBounceBreak === true) {
|
|
363
|
+
entryTiming = "stale_reaction";
|
|
364
|
+
} else if (lastSample?.rejectionBar === true) {
|
|
365
|
+
entryTiming = "ready_rejection";
|
|
366
|
+
} else if (followThroughReady) {
|
|
367
|
+
entryTiming = "ready_follow_through";
|
|
368
|
+
} else if (lastSample?.lineTouched === true && lastSample.closeOnBounceSide === true && !lastSample.rejectionBar) {
|
|
369
|
+
entryTiming = "wait_reaction_confirmation";
|
|
370
|
+
} else if (staleReaction) {
|
|
371
|
+
entryTiming = "stale_reaction";
|
|
372
|
+
} else {
|
|
373
|
+
entryTiming = "wait_touch";
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
rejectionDetected: latestRejectionIndex != null,
|
|
377
|
+
barsSinceRejection,
|
|
378
|
+
rejectionFresh,
|
|
379
|
+
followThroughReady,
|
|
380
|
+
staleReaction,
|
|
381
|
+
entryTiming,
|
|
382
|
+
entryReadyNow: entryTiming === "ready_rejection" || entryTiming === "ready_follow_through",
|
|
383
|
+
currentRejectionStrengthPct: lastSample?.rejectionStrengthPct ?? null,
|
|
384
|
+
previousRejectionStrengthPct: prevSample?.rejectionStrengthPct ?? null
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// src/ReverseTrendLine/adapters/ai.ts
|
|
389
|
+
var REVERSE_TRENDLINE_CONTEXT_PROMPT = `
|
|
390
|
+
\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F ReverseTrendLine:
|
|
391
|
+
- \u042D\u0442\u043E \u043D\u0435 breakout-\u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F, \u0430 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F \u043D\u0430 \u043E\u0442\u0441\u043A\u043E\u043A \u043E\u0442 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438.
|
|
392
|
+
- \u0414\u043B\u044F LONG \u043F\u043E support line (trendline.mode="lows") \u043D\u0443\u0436\u0435\u043D \u043A\u0430\u0441\u0430\u043D\u0438\u0435/\u043B\u043E\u0436\u043D\u044B\u0439 \u043F\u0440\u043E\u043A\u043E\u043B \u043B\u0438\u043D\u0438\u0438 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u044F \u0432\u044B\u0448\u0435 \u043D\u0435\u0435.
|
|
393
|
+
- \u0414\u043B\u044F SHORT \u043F\u043E resistance line (trendline.mode="highs") \u043D\u0443\u0436\u0435\u043D \u043A\u0430\u0441\u0430\u043D\u0438\u0435/\u043B\u043E\u0436\u043D\u044B\u0439 \u043F\u0440\u043E\u043A\u043E\u043B \u043B\u0438\u043D\u0438\u0438 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u044F \u043D\u0438\u0436\u0435 \u043D\u0435\u0435.
|
|
394
|
+
- \u0415\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u0443\u0436\u0435 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E \u043F\u0440\u043E\u0431\u0438\u043B\u0430 \u043B\u0438\u043D\u0438\u044E \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443, \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u0443\u044E \u043E\u0442\u0441\u043A\u043E\u043A\u0443, \u044D\u0442\u043E \u043D\u0435 bounce setup: direction=null \u0438 quality <= 2.
|
|
395
|
+
- \u0414\u043B\u044F bounce-\u0441\u0435\u0442\u0430\u043F\u043E\u0432 \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0440\u0435\u0430\u043A\u0446\u0438\u044F \u0441\u0432\u0435\u0447\u0438 \u043D\u0430 \u043B\u0438\u043D\u0438\u0438, rejection wick, \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u044F \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0438 follow-through \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0431\u0430\u0440\u0435.
|
|
396
|
+
- \u0415\u0441\u043B\u0438 payload.additionalIndicators.reverseTrendlineContext.failedBounceBreak=true, \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C.
|
|
397
|
+
- \u0415\u0441\u043B\u0438 payload.additionalIndicators.reverseTrendlineContext.entryTiming \u043D\u0435 \u0440\u0430\u0432\u0435\u043D ready_rejection \u0438\u043B\u0438 ready_follow_through, \u043E\u0431\u044B\u0447\u043D\u043E quality <= 3.
|
|
398
|
+
- \u0411\u0430\u0437\u043E\u0432\u044B\u0439 deterministic approve \u0434\u043B\u044F same-bar rejection \u0434\u0430\u0435\u0442\u0441\u044F \u043D\u0435 \u0432\u0441\u0435\u043C:
|
|
399
|
+
- \u0441\u0438\u043B\u044C\u043D\u044B\u0439 conflict-only rejection \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C quality=4;
|
|
400
|
+
- \u0447\u0430\u0441\u0442\u044C same-bar rejection \u0441 conflictState=none \u0438\u043B\u0438 both \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C quality=4 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u0438 \u043E\u0447\u0435\u043D\u044C \u0441\u0438\u043B\u044C\u043D\u043E\u043C deterministic rejection score.
|
|
401
|
+
- \u0414\u043B\u044F SHORT bounce setup \u0441 btc_bias_conflict \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality: \u0442\u0430\u043A\u0438\u0435 \u043A\u0435\u0439\u0441\u044B \u043E\u0431\u044B\u0447\u043D\u043E \u043E\u0441\u0442\u0430\u044E\u0442\u0441\u044F watch, \u0435\u0441\u043B\u0438 \u043D\u0435\u0442 \u0433\u043E\u0440\u0430\u0437\u0434\u043E \u0431\u043E\u043B\u0435\u0435 \u0441\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
|
|
402
|
+
- \u0415\u0441\u043B\u0438 deterministicRejectionScore \u043D\u0438\u0437\u043A\u0438\u0439 \u0438\u043B\u0438 \u0441\u0440\u0435\u0434\u043D\u0438\u0439, \u043D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 quality=4 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0441\u0432\u0435\u0447\u0430 \u0432\u0438\u0437\u0443\u0430\u043B\u044C\u043D\u043E \u043F\u043E\u0445\u043E\u0436\u0430 \u043D\u0430 rejection.
|
|
403
|
+
`;
|
|
404
|
+
var REVERSE_TRENDLINE_PAYLOAD_PROMPT = `
|
|
405
|
+
- \u0412 payload.figures.trendline \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044F \u043B\u0438\u043D\u0438\u0438.
|
|
406
|
+
- \u0412 payload.additionalIndicators.reverseTrendlineContext \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u043A\u0440\u0430\u0442\u043A\u0430\u044F \u0441\u0432\u043E\u0434\u043A\u0430 bounce-\u043B\u043E\u0433\u0438\u043A\u0438: \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435, \u0440\u0430\u0441\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0446\u0435\u043D\u044B \u0434\u043E \u043B\u0438\u043D\u0438\u0438, \u0431\u044B\u043B \u043B\u0438 \u043A\u0430\u0441\u0430\u043D\u0438\u0435 \u043B\u0438\u043D\u0438\u0438, \u0431\u044B\u043B\u0430 \u043B\u0438 rejection-\u0441\u0432\u0435\u0447\u0430, \u0441\u0438\u043B\u0443 rejection, timing-stage, \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B bias \u0438 deterministicRejectionScore.
|
|
407
|
+
`;
|
|
408
|
+
var getReverseTrendlineBiasConflictState = (context) => {
|
|
409
|
+
const coinConflict = context.coinBiasAligned === false;
|
|
410
|
+
const btcConflict = context.btcBiasAligned === false;
|
|
411
|
+
if (coinConflict && btcConflict) {
|
|
412
|
+
return "both";
|
|
413
|
+
}
|
|
414
|
+
if (coinConflict) {
|
|
415
|
+
return "coin_only";
|
|
416
|
+
}
|
|
417
|
+
if (btcConflict) {
|
|
418
|
+
return "btc_only";
|
|
419
|
+
}
|
|
420
|
+
if (context.coinBiasAligned === true && context.btcBiasAligned === true) {
|
|
421
|
+
return "none";
|
|
422
|
+
}
|
|
423
|
+
return "unknown";
|
|
424
|
+
};
|
|
425
|
+
var getDeterministicReverseTrendlineQuality = (context) => {
|
|
426
|
+
if (context.hardBlockReasons.length > 0) {
|
|
427
|
+
return 2;
|
|
428
|
+
}
|
|
429
|
+
if (context.entryTiming !== "ready_rejection" && context.entryTiming !== "ready_follow_through") {
|
|
430
|
+
return 3;
|
|
431
|
+
}
|
|
432
|
+
const rejectionStrengthPct = context.rejectionStrengthPct ?? 0;
|
|
433
|
+
const rejectionWickPct = context.rejectionWickPct ?? 0;
|
|
434
|
+
const touches = context.touches ?? 0;
|
|
435
|
+
const distance = context.distance ?? Number.POSITIVE_INFINITY;
|
|
436
|
+
const biasConflictState = getReverseTrendlineBiasConflictState(context);
|
|
437
|
+
const noConflict = biasConflictState === "none";
|
|
438
|
+
const conflictOnly = biasConflictState === "coin_only" || biasConflictState === "btc_only";
|
|
439
|
+
const quality5 = context.entryTiming === "ready_follow_through" && noConflict && rejectionStrengthPct >= 0.25 && rejectionWickPct >= 0.18 && touches >= 4 && distance < 500;
|
|
440
|
+
if (quality5) {
|
|
441
|
+
return 5;
|
|
442
|
+
}
|
|
443
|
+
const quality4FollowThrough = context.entryTiming === "ready_follow_through" && noConflict && rejectionStrengthPct >= 0.22 && rejectionWickPct >= 0.18 && touches >= 4;
|
|
444
|
+
if (quality4FollowThrough) {
|
|
445
|
+
return 4;
|
|
446
|
+
}
|
|
447
|
+
const quality4ConflictRejection = context.entryTiming === "ready_rejection" && conflictOnly && rejectionStrengthPct >= 0.45 && touches >= 5 && !(context.signalDirection === "SHORT" && biasConflictState === "btc_only");
|
|
448
|
+
if (quality4ConflictRejection) {
|
|
449
|
+
return 4;
|
|
450
|
+
}
|
|
451
|
+
const rejectionScore = getDeterministicReverseTrendlineRejectionScore(context);
|
|
452
|
+
const quality4ScoredRejection = context.entryTiming === "ready_rejection" && (biasConflictState === "none" || biasConflictState === "both") && rejectionScore != null && rejectionScore >= 7;
|
|
453
|
+
if (quality4ScoredRejection) {
|
|
454
|
+
return 4;
|
|
455
|
+
}
|
|
456
|
+
const quality4EliteAlignedRejection = context.entryTiming === "ready_rejection" && noConflict && rejectionStrengthPct >= 0.9 && rejectionWickPct >= 0.15 && touches >= 5 && distance <= 250;
|
|
457
|
+
return quality4EliteAlignedRejection ? 4 : 3;
|
|
458
|
+
};
|
|
459
|
+
var getDeterministicReverseTrendlineRejectionScore = (context) => {
|
|
460
|
+
if (context.entryTiming !== "ready_rejection") {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
const biasConflictState = getReverseTrendlineBiasConflictState(context);
|
|
464
|
+
const rejectionStrengthPct = context.rejectionStrengthPct ?? 0;
|
|
465
|
+
const rejectionWickPct = context.rejectionWickPct ?? 0;
|
|
466
|
+
const touches = context.touches ?? 0;
|
|
467
|
+
const distance = context.distance ?? Number.POSITIVE_INFINITY;
|
|
468
|
+
let score = 0;
|
|
469
|
+
if (rejectionStrengthPct >= 0.25) {
|
|
470
|
+
score += 1;
|
|
471
|
+
}
|
|
472
|
+
if (rejectionStrengthPct >= 0.6) {
|
|
473
|
+
score += 1;
|
|
474
|
+
}
|
|
475
|
+
if (rejectionWickPct >= 0.18) {
|
|
476
|
+
score += 1;
|
|
477
|
+
}
|
|
478
|
+
if (touches >= 4) {
|
|
479
|
+
score += 1;
|
|
480
|
+
}
|
|
481
|
+
if (distance <= 250) {
|
|
482
|
+
score += 1;
|
|
483
|
+
}
|
|
484
|
+
if (context.signalDirection === "LONG") {
|
|
485
|
+
if (biasConflictState === "both") {
|
|
486
|
+
score += 1;
|
|
487
|
+
}
|
|
488
|
+
if (rejectionWickPct >= 0.75) {
|
|
489
|
+
score += 1;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (context.signalDirection === "SHORT") {
|
|
493
|
+
if (biasConflictState === "none") {
|
|
494
|
+
score += 1;
|
|
495
|
+
}
|
|
496
|
+
if (distance <= 150) {
|
|
497
|
+
score += 1;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return score;
|
|
501
|
+
};
|
|
502
|
+
var buildReverseTrendlineAiContext = (signal) => {
|
|
503
|
+
const structural = buildReverseTrendlineStructuralContext(signal);
|
|
504
|
+
const computedTiming = buildReverseTrendlineTimingContext({ signal });
|
|
505
|
+
const timingFromSignal = typeof signal.additionalIndicators?.reverseTrendlineTiming === "object" && signal.additionalIndicators?.reverseTrendlineTiming && typeof signal.additionalIndicators.reverseTrendlineTiming.entryTiming === "string" ? signal.additionalIndicators.reverseTrendlineTiming : null;
|
|
506
|
+
const timing = timingFromSignal ? {
|
|
507
|
+
...computedTiming,
|
|
508
|
+
...timingFromSignal,
|
|
509
|
+
entryReadyNow: timingFromSignal.entryTiming === "ready_rejection" || timingFromSignal.entryTiming === "ready_follow_through"
|
|
510
|
+
} : computedTiming;
|
|
511
|
+
const hardBlockReasons = [...structural.structuralHardBlockReasons];
|
|
512
|
+
const deterministicRejectionScore = getDeterministicReverseTrendlineRejectionScore({
|
|
513
|
+
...structural,
|
|
514
|
+
...timing,
|
|
515
|
+
hardBlockReasons
|
|
516
|
+
});
|
|
517
|
+
const deterministicQuality = getDeterministicReverseTrendlineQuality({
|
|
518
|
+
...structural,
|
|
519
|
+
...timing,
|
|
520
|
+
hardBlockReasons
|
|
521
|
+
});
|
|
522
|
+
return {
|
|
523
|
+
...structural,
|
|
524
|
+
...timing,
|
|
525
|
+
deterministicQuality,
|
|
526
|
+
deterministicRejectionScore,
|
|
527
|
+
approvalAllowedNow: deterministicQuality >= 4,
|
|
528
|
+
hardBlockReasons
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
var getReverseTrendlineContextFromPayload = (payload, signal) => {
|
|
532
|
+
const additional = payload.additionalIndicators;
|
|
533
|
+
const fromPayload = additional?.reverseTrendlineContext;
|
|
534
|
+
return fromPayload ?? buildReverseTrendlineAiContext(signal);
|
|
535
|
+
};
|
|
536
|
+
var getHardBlockReasonText = (reason) => {
|
|
537
|
+
switch (reason) {
|
|
538
|
+
case "failed_bounce_break":
|
|
539
|
+
return "\u0446\u0435\u043D\u0430 \u043F\u0440\u043E\u0431\u0438\u043B\u0430 \u043B\u0438\u043D\u0438\u044E \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443, \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u0443\u044E \u043E\u0442\u0441\u043A\u043E\u043A\u0443";
|
|
540
|
+
case "coin_bias_conflict":
|
|
541
|
+
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 bounce-\u0441\u0434\u0435\u043B\u043A\u0438";
|
|
542
|
+
case "btc_bias_conflict":
|
|
543
|
+
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 bounce-\u0441\u0434\u0435\u043B\u043A\u0438";
|
|
544
|
+
default:
|
|
545
|
+
return reason;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
var reverseTrendLineAiAdapter = {
|
|
549
|
+
buildPayload: ({ signal, basePayload }) => ({
|
|
550
|
+
...basePayload,
|
|
551
|
+
figures: {
|
|
552
|
+
...basePayload.figures,
|
|
553
|
+
trendline: getTrendLineFromPayload(signal)
|
|
554
|
+
},
|
|
555
|
+
additionalIndicators: {
|
|
556
|
+
...basePayload.additionalIndicators,
|
|
557
|
+
reverseTrendlineContext: buildReverseTrendlineAiContext(signal)
|
|
558
|
+
}
|
|
559
|
+
}),
|
|
560
|
+
postProcessAnalysis: ({ signal, payload, analysis }) => {
|
|
561
|
+
const context = getReverseTrendlineContextFromPayload(payload, signal);
|
|
562
|
+
const signalDirection = signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
|
|
563
|
+
if (context.approvalAllowedNow === true && signalDirection != null) {
|
|
564
|
+
return {
|
|
565
|
+
...analysis,
|
|
566
|
+
direction: signalDirection,
|
|
567
|
+
quality: context.deterministicQuality,
|
|
568
|
+
needRetest: false,
|
|
569
|
+
retestPrice: null,
|
|
570
|
+
takeProfitPrice: analysis.takeProfitPrice ?? signal.prices?.takeProfitPrice ?? null,
|
|
571
|
+
stopLossPrice: analysis.stopLossPrice ?? signal.prices?.stopLossPrice ?? null
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
...analysis,
|
|
576
|
+
direction: null,
|
|
577
|
+
quality: context.deterministicQuality,
|
|
578
|
+
needRetest: true,
|
|
579
|
+
retestPrice: context.currentLinePrice ?? null,
|
|
580
|
+
takeProfitPrice: null,
|
|
581
|
+
stopLossPrice: null,
|
|
582
|
+
qualityReason: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "ReverseTrendLine deterministic quality: \u0434\u043B\u044F bounce \u043D\u0443\u0436\u0435\u043D \u043B\u0438\u0431\u043E \u0441\u0438\u043B\u044C\u043D\u044B\u0439 conflict-only rejection, \u043B\u0438\u0431\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u0439 aligned follow-through.",
|
|
583
|
+
triggerInvalidation: context.hardBlockReasons.length > 0 ? `\u0416\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 bounce setup: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "\u0416\u0434\u0430\u0442\u044C \u043A\u0430\u0441\u0430\u043D\u0438\u0435 \u043B\u0438\u043D\u0438\u0438, rejection-\u0441\u0432\u0435\u0447\u0443 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u044F \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u043B\u0438\u043D\u0438\u0438.",
|
|
584
|
+
comment: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B \u0432\u0445\u043E\u0434: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "ReverseTrendLine \u043F\u043E\u043A\u0430 \u043F\u0435\u0440\u0435\u0432\u043E\u0434\u0438\u0442 \u0441\u0435\u0442\u0430\u043F \u0432 watch \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u043E\u0442\u0441\u043A\u043E\u043A\u0430."
|
|
585
|
+
};
|
|
586
|
+
},
|
|
587
|
+
buildSystemPromptAddon: () => `${REVERSE_TRENDLINE_CONTEXT_PROMPT}
|
|
588
|
+
${REVERSE_TRENDLINE_PAYLOAD_PROMPT}`,
|
|
589
|
+
buildHumanPromptAddon: ({ signal, payload }) => {
|
|
590
|
+
const context = getReverseTrendlineContextFromPayload(payload, signal);
|
|
591
|
+
return `
|
|
592
|
+
|
|
593
|
+
\u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 ReverseTrendLine:
|
|
594
|
+
- entryTiming=${context.entryTiming}
|
|
595
|
+
- lineTouchedNow=${context.lineTouchedNow}
|
|
596
|
+
- closeOnBounceSide=${context.closeOnBounceSide}
|
|
597
|
+
- failedBounceBreak=${context.failedBounceBreak}
|
|
598
|
+
- rejectionWickPct=${context.rejectionWickPct?.toFixed?.(3) ?? "n/a"}%
|
|
599
|
+
- rejectionStrengthPct=${context.rejectionStrengthPct?.toFixed?.(3) ?? "n/a"}%
|
|
600
|
+
- touches=${context.touches ?? "n/a"}
|
|
601
|
+
- distance=${context.distance ?? "n/a"}
|
|
602
|
+
- coinBiasAligned=${context.coinBiasAligned}
|
|
603
|
+
- btcBiasAligned=${context.btcBiasAligned}
|
|
604
|
+
- deterministicRejectionScore=${context.deterministicRejectionScore ?? "n/a"}
|
|
605
|
+
- approvalAllowedNow=${context.approvalAllowedNow}
|
|
606
|
+
- hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
|
|
607
|
+
|
|
608
|
+
\u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F ReverseTrendLine:
|
|
609
|
+
- \u0438\u0441\u043A\u0430\u0442\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0440\u0435\u0430\u043A\u0446\u0438\u0438 \u043E\u0442 \u043B\u0438\u043D\u0438\u0438, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0431\u043E\u044F \u0447\u0435\u0440\u0435\u0437 \u043B\u0438\u043D\u0438\u044E;
|
|
610
|
+
- \u0435\u0441\u043B\u0438 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C failedBounceBreak=true, \u043D\u0435 \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;
|
|
611
|
+
- \u0435\u0441\u043B\u0438 setup \u0435\u0449\u0435 \u0432 \u0441\u0442\u0430\u0434\u0438\u0438 wait_touch / wait_reaction_confirmation / stale_reaction, \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0442\u044C quality.
|
|
612
|
+
- \u0435\u0441\u043B\u0438 deterministicRejectionScore \u0432\u044B\u0441\u043E\u043A\u0438\u0439, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0435\u0433\u043E \u043A\u0430\u043A \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u043C bounce-\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043E\u043C, \u0430 \u043D\u0435 \u043A\u0430\u043A \u0437\u0430\u043C\u0435\u043D\u0443 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.
|
|
613
|
+
`;
|
|
614
|
+
},
|
|
615
|
+
mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
|
|
616
|
+
config2
|
|
617
|
+
)
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
// src/ReverseTrendLine/hooks.ts
|
|
621
|
+
import { createCloseOppositeBeforePlaceOrderHook } from "@tradejs/node/strategies";
|
|
622
|
+
var reverseTrendLineBeforePlaceOrderHook = createCloseOppositeBeforePlaceOrderHook({
|
|
623
|
+
isEnabled: (config2) => Boolean(config2.CLOSE_OPPOSITE_POSITIONS)
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// src/ReverseTrendLine/manifest.ts
|
|
627
|
+
var reverseTrendLineManifest = {
|
|
628
|
+
name: "ReverseTrendLine",
|
|
629
|
+
hooks: {
|
|
630
|
+
beforePlaceOrder: reverseTrendLineBeforePlaceOrderHook
|
|
631
|
+
},
|
|
632
|
+
aiAdapter: reverseTrendLineAiAdapter
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// src/ReverseTrendLine/config.ts
|
|
636
|
+
var config = {
|
|
637
|
+
ENV: "BACKTEST",
|
|
638
|
+
INTERVAL: "15",
|
|
639
|
+
MAKE_ORDERS: true,
|
|
640
|
+
CLOSE_OPPOSITE_POSITIONS: false,
|
|
641
|
+
BACKTEST_PRICE_MODE: "mid",
|
|
642
|
+
AI_ENABLED: false,
|
|
643
|
+
MIN_AI_QUALITY: 3,
|
|
644
|
+
FEE_PERCENT: 5e-3,
|
|
645
|
+
MAX_LOSS_VALUE: 10,
|
|
646
|
+
MA_FAST: 14,
|
|
647
|
+
MA_MEDIUM: 49,
|
|
648
|
+
MA_SLOW: 50,
|
|
649
|
+
OBV_SMA: 10,
|
|
650
|
+
ATR: 14,
|
|
651
|
+
ATR_PCT_SHORT: 7,
|
|
652
|
+
ATR_PCT_LONG: 30,
|
|
653
|
+
BB: 20,
|
|
654
|
+
BB_STD: 2,
|
|
655
|
+
MACD_FAST: 12,
|
|
656
|
+
MACD_SLOW: 26,
|
|
657
|
+
MACD_SIGNAL: 9,
|
|
658
|
+
TRENDLINE: {
|
|
659
|
+
minTouches: 4,
|
|
660
|
+
offset: 3,
|
|
661
|
+
epsilon: 3e-3,
|
|
662
|
+
epsilonOffset: 4e-3
|
|
663
|
+
},
|
|
664
|
+
HIGHS: {
|
|
665
|
+
enable: true,
|
|
666
|
+
direction: "SHORT",
|
|
667
|
+
TP: 3.2,
|
|
668
|
+
SL: 1.1,
|
|
669
|
+
minRiskRatio: 1.6
|
|
670
|
+
},
|
|
671
|
+
LOWS: {
|
|
672
|
+
enable: true,
|
|
673
|
+
direction: "LONG",
|
|
674
|
+
TP: 3.2,
|
|
675
|
+
SL: 1.1,
|
|
676
|
+
minRiskRatio: 1.6
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
export {
|
|
681
|
+
toFiniteNumberOrNull,
|
|
682
|
+
buildReverseTrendlineStructuralContext,
|
|
683
|
+
buildReverseTrendlineTimingContext,
|
|
684
|
+
reverseTrendLineAiAdapter,
|
|
685
|
+
reverseTrendLineManifest,
|
|
686
|
+
config
|
|
687
|
+
};
|