scripts/grid.js Repository BOT_EU Original path scripts/grid.jsRole SOURCE Size 24318 bytes Lines 749 SHA-256 a4f1ec6e389f547a2aba0fff78b3267f740682ef4d3be8505d27313b1b4e37f8Displayed range 501–749 Previous file/page · Project index · Next file/page
const haloK = haloOrdersCount(sNorm);
const gapPct = gapPctToNeighbor(below, i);
// BOT 8.6: HALO_SPACING_ALPHA=0.9, GAP_SPACING_SCALE=0 (hardcoded)
const spacingPct = computeSpacingPct({
baseSpacingPct: buyBaseSpacingPct,
alpha: 0.9,
sNorm,
gapPct,
gapScale: 0,
});
buyPrices.push(c.cluster_price);
const maxBetween = Math.min(CONFIG.MAX_ORDERS_BETWEEN_CLUSTERS ?? 10, haloK);
buyPrices.push(
...buildHaloPricesTowardMid({
clusterPrice: c.cluster_price,
midPrice: mid,
side: "Buy",
spacingPct,
maxBetween,
})
);
}
for (let i = 0; i < above.length; i++) {
const c = above[i];
const sNorm = normStrength(Number(c.cluster_strength), sMin, sMax);
const haloK = haloOrdersCount(sNorm);
const gapPct = gapPctToNeighbor(above, i);
// BOT 8.6: HALO_SPACING_ALPHA=0.9, GAP_SPACING_SCALE=0 (hardcoded)
const spacingPct = computeSpacingPct({
baseSpacingPct: sellBaseSpacingPct,
alpha: 0.9,
sNorm,
gapPct,
gapScale: 0,
});
sellPrices.push(c.cluster_price);
const maxBetween = Math.min(CONFIG.MAX_ORDERS_BETWEEN_CLUSTERS ?? 10, haloK);
sellPrices.push(
...buildHaloPricesTowardMid({
clusterPrice: c.cluster_price,
midPrice: mid,
side: "Sell",
spacingPct,
maxBetween,
})
);
}
let buyCandidates = sortByPriceAsc(uniqPrices(buyPrices)).filter((p) => p < mid);
let sellCandidates = sortByPriceAsc(uniqPrices(sellPrices)).filter((p) => p > mid);
buyCandidates = extendCandidatesIfNeeded({
side: "Buy",
mid,
baseSpacingPct: buyBaseSpacingPct,
candidatesAsc: buyCandidates,
needAtLeast: Math.max(buyMax, 12),
});
sellCandidates = extendCandidatesIfNeeded({
side: "Sell",
mid,
baseSpacingPct: sellBaseSpacingPct,
candidatesAsc: sellCandidates,
needAtLeast: Math.max(sellMax, 12),
});
// neutral (unadapted) notional — used as decay target for distant levels
const neutralNotional = Math.max(effectiveMinOrder, perOrder);
// optional weighted picking
const buyWeightFn = (p) => {
const decayed = decayTowardNeutral({ adaptiveNotional: baseNotionalBuy, neutralNotional, price: p, mid, spacingPct: buyBaseSpacingPct });
const n = notionalForLevel({
side: "Buy",
price: p,
clustersSide: below,
mu,
sd,
baseSpacingPct: buyBaseSpacingPct,
baseNotional: decayed,
minOrder: effectiveMinOrder,
});
return n / Math.max(1e-12, decayed);
};
const sellWeightFn = (p) => {
const decayed = decayTowardNeutral({ adaptiveNotional: baseNotionalSell, neutralNotional, price: p, mid, spacingPct: sellBaseSpacingPct });
const n = notionalForLevel({
side: "Sell",
price: p,
clustersSide: above,
mu,
sd,
baseSpacingPct: sellBaseSpacingPct,
baseNotional: decayed,
minOrder: effectiveMinOrder,
});
return n / Math.max(1e-12, decayed);
};
const buyFinal = pickLevels({
candidatesAsc: buyCandidates,
takeN: Math.min(buyMax, CONFIG.MAX_LEVELS_PER_SIDE ?? 30),
side: "Buy",
mid,
baseSpacingPct: buyBaseSpacingPct,
weightFn: buyWeightFn,
});
const sellFinal = pickLevels({
candidatesAsc: sellCandidates,
takeN: Math.min(sellMax, CONFIG.MAX_LEVELS_PER_SIDE ?? 30),
side: "Sell",
mid,
baseSpacingPct: sellBaseSpacingPct,
weightFn: sellWeightFn,
});
const orders = [];
const usedBuy = new Set();
const usedSell = new Set();
// BUY orders
let remainingQuote = buyBudgetTotal;
for (const p of buyFinal.sort((a, b) => b - a)) {
const kk = key6(p);
if (usedBuy.has(kk)) continue;
if (remainingQuote <= 0) break; // no budget -> stop
const buyDecayed = decayTowardNeutral({ adaptiveNotional: baseNotionalBuy, neutralNotional, price: p, mid, spacingPct: buyBaseSpacingPct });
let notional = notionalForLevel({
side: "Buy",
price: p,
clustersSide: below,
mu,
sd,
baseSpacingPct: buyBaseSpacingPct,
baseNotional: buyDecayed,
minOrder: effectiveMinOrder,
});
// if weight formula returns non-positive (possible when minOrder=0)
if (!Number.isFinite(notional) || notional <= 0) break;
if (remainingQuote < effectiveMinOrder) break;
if (notional > remainingQuote) {
notional = remainingQuote;
if (notional < effectiveMinOrder) break;
}
// if rounding or adjustment shrinks to zero, bail out as well
if (!(Number.isFinite(notional) && notional > 0)) break;
orders.push({
side: "Buy",
price: p,
qty: notional / p,
notionalQuote: notional,
origin: "GRID",
clusterPrice: null,
isCluster: below.some((c) => isNear(c.cluster_price, p)),
});
usedBuy.add(kk);
remainingQuote -= notional;
const buyCount = orders.reduce((acc, o) => acc + (o.side === "Buy" ? 1 : 0), 0);
if (buyCount >= (CONFIG.MAX_LEVELS_PER_SIDE ?? 30)) break;
}
// SELL orders
let remainingBase = baseFree;
for (const p of sellFinal.sort((a, b) => a - b)) {
const kk = key6(p);
if (usedSell.has(kk)) continue;
if (remainingBase <= 0) break; // budget exhausted
const sellDecayed = decayTowardNeutral({ adaptiveNotional: baseNotionalSell, neutralNotional, price: p, mid, spacingPct: sellBaseSpacingPct });
let notional = notionalForLevel({
side: "Sell",
price: p,
clustersSide: above,
mu,
sd,
baseSpacingPct: sellBaseSpacingPct,
baseNotional: sellDecayed,
minOrder: effectiveMinOrder,
});
if (!Number.isFinite(notional) || notional <= 0) break;
if (notional < effectiveMinOrder) notional = effectiveMinOrder;
const qty = notional / p;
// ✅ KLUCZOWA POPRAWKA:
// jeśli qty > remainingBase, to NIE "break",
// bo przy wyższych cenach qty maleje i może się zmieścić.
if (qty > remainingBase) {
continue;
}
orders.push({
side: "Sell",
price: p,
qty,
notionalQuote: qty * p,
origin: "GRID",
clusterPrice: null,
isCluster: above.some((c) => isNear(c.cluster_price, p)),
});
usedSell.add(kk);
remainingBase -= qty;
const sellCount = orders.reduce((acc, o) => acc + (o.side === "Sell" ? 1 : 0), 0);
if (sellCount >= (CONFIG.MAX_LEVELS_PER_SIDE ?? 30)) break;
if (remainingBase <= 0) break;
}
return {
feeGuardPct,
orders,
policyApplied: {
allowNewBuys,
allowNewSells,
capitalUsagePct,
spacingMultBuy,
spacingMultSell,
orderCountMultBuy,
orderCountMultSell,
orderQuoteBase: perOrder,
orderQuoteBuy: perOrderBuy,
orderQuoteSell: perOrderSell,
},
anchorsUsed,
majorBelow: pickMajor(belowAll),
majorAbove: pickMajor(aboveAll),
};
}