💬 Наш обученный чат для конструктора: Войти в GPT

Если вы понимаете, какие условия нужны, просто сформулируйте их словами.

Логика для бота:
— BUY: EMA растёт, свеча закрылась выше максимума подтверждённого фрактала + ATR×коэф, тело свечи ≥ minBodyRatio
— SELL: EMA падает, свеча закрылась ниже минимума подтверждённого фрактала − ATR×коэф, тело свечи ≥ minBodyRatio
export const meta = {
key: "fractal-break-ema-a",
name: "🗡️ Fractal Edge Breakout",
defaultParams: {
K: 5, // окно поиска подтверждённых фракталов среди последних K закрытых баров
emaPeriod: 20,
atrPeriod: 14,
breakoutAtrMult: 0.1, // смещение пробоя относительно FH/FL в долях ATR
minBodyRatio: 0.5 // |Close-Open| >= 0.5*(High-Low)
},
paramMeta: {
K: { label: "Fractal window (K)", type: "number", min: 3, max: 20 },
emaPeriod: { label: "EMA period", type: "number", min: 5, max: 200 },
atrPeriod: { label: "ATR period", type: "number", min: 5, max: 100 },
breakoutAtrMult: { label: "Breakout × ATR", type: "number", min: 0, max: 1 },
minBodyRatio: { label: "Min body / range", type: "number", min: 0, max: 1 }
}
};
export function init(ctx) {
const { candleSeries, isDark, params, createSeriesMarkers } = ctx;
const markersApi = createSeriesMarkers(candleSeries, []);
function update(candles) {
// candles: [{ time, open, high, low, close }, ...]
const p = params || meta.defaultParams;
const len = candles.length;
const TIref = (typeof TI !== "undefined") ? TI : (typeof window !== "undefined" ? window.TI : null);
if (!TIref || len < Math.max(p.emaPeriod + 2, p.atrPeriod + 2, 7)) {
markersApi.setMarkers([]);
return [];
}
const opens = candles.map(c => c.open);
const highs = candles.map(c => c.high);
const lows = candles.map(c => c.low);
const closes = candles.map(c => c.close);
// === EMA ===
const emaRaw = TIref.EMA.calculate({ period: p.emaPeriod, values: closes }) || [];
const ema = Array(len).fill(null);
for (let i = 0; i < emaRaw.length; i++) ema[i + (p.emaPeriod - 1)] = emaRaw[i];
// === ATR ===
const atrRaw = TIref.ATR.calculate({ period: p.atrPeriod, high: highs, low: lows, close: closes }) || [];
const atr = Array(len).fill(null);
for (let i = 0; i < atrRaw.length; i++) atr[i + p.atrPeriod] = atrRaw[i];
// === Фракталы Bill Williams 2/2 (подтверждаются лишь спустя 2 бара) ===
const fractHigh = Array(len).fill(false);
const fractLow = Array(len).fill(false);
for (let i = 2; i <= len - 3; i++) {
const h = highs[i];
const l = lows[i];
if (
h != null &&
h > highs[i - 1] && h > highs[i - 2] &&
h > highs[i + 1] && h > highs[i + 2]
) fractHigh[i] = true;
if (
l != null &&
l < lows[i - 1] && l < lows[i - 2] &&
l < lows[i + 1] && l < lows[i + 2]
) fractLow[i] = true;
}
const markers = [];
const K = Math.max(3, p.K);
// Итерируем по закрытым барам
for (let i = Math.max(p.emaPeriod, p.atrPeriod, 6); i < len; i++) {
const cNow = candles[i];
const emaNow = ema[i], emaPrev = ema[i - 1];
const atrNow = atr[i];
if (emaNow == null || emaPrev == null || atrNow == null) continue;
const range = Math.max(0, highs[i] - lows[i]);
const body = Math.abs(closes[i] - opens[i]);
const bodyOK = range > 0 && (body >= p.minBodyRatio * range);
// --- Поиск подтвержденных фракталов среди последних K закрытых баров ---
// Рассматриваем окно последних K баров: [i-K+1 .. i]
// Подтвержденные фракталы в этом окне должны быть с индексом <= i-3 (т.е. j <= i-3)
const jStart = Math.max(2, i - K + 1);
const jEnd = i - 3;
if (jStart > jEnd) continue; // нет подтвержденных фракталов в окне
let FH = -Infinity;
let hasFH = false;
let FL = Infinity;
let hasFL = false;
for (let j = jStart; j <= jEnd; j++) {
if (fractHigh[j]) { hasFH = true; if (highs[j] > FH) FH = highs[j]; }
if (fractLow[j]) { hasFL = true; if (lows[j] < FL) FL = lows[j]; }
}
// --- BUY ---
if (hasFH && (emaNow > emaPrev) && bodyOK) {
const buyLevel = FH + p.breakoutAtrMult * atrNow;
if (closes[i] > buyLevel) {
markers.push({
name: "buy",
time: cNow.time,
position: "belowBar",
shape: "arrowUp",
color: "#16a34a",
price: closes[i],
text: "BUY"
});
continue;
}
}
// --- SELL ---
if (hasFL && (emaNow < emaPrev) && bodyOK) {
const sellLevel = FL - p.breakoutAtrMult * atrNow;
if (closes[i] < sellLevel) {
markers.push({
name: "sell",
time: cNow.time,
position: "aboveBar",
shape: "arrowDown",
color: "#ef4444",
price: closes[i],
text: "SELL"
});
}
}
}
markersApi.setMarkers(markers);
return markers;
}
function destroy() { markersApi.setMarkers([]); }
return { update, destroy };
}