Skip to main content

Keltner Channel Breakout strategy

This breakout example calculates Keltner Channels from typical price and ATR, plus On-Balance Volume from candle close changes. It logs the current channel values, OBV, and recent candle data before evaluating entries.

With the 8-second cooldown satisfied, the code buys when OBV is rising and the close is above the upper channel with no bag, and sells when OBV is falling and the close is below the lower channel while holding a bag.

tip

This example strategy is machine generated using Gunbot AI. Review its behavior carefully in a simulated bot instance before using parts of this code in production.

// initialize customStratStore within pairLedger object
gb.data.pairLedger.customStratStore = gb.data.pairLedger.customStratStore || {};

// forced wait time reduces risk of double orders
function checkTime() {
return !gb.data.pairLedger.customStratStore.timeCheck || typeof gb.data.pairLedger.customStratStore.timeCheck !== "number"
? (gb.data.pairLedger.customStratStore.timeCheck = Date.now(), false)
: (Date.now() - gb.data.pairLedger.customStratStore.timeCheck > 8000);
}
const enoughTimePassed = checkTime();

// set timestamp for checkTime in next round
const setTimestamp = () => gb.data.pairLedger.customStratStore.timeCheck = Date.now();

// calculate Keltner Channels
const calculateKC = (period = 20, multiplier = 2) => {
const tp = gb.data.candlesHigh.map((high, i) => (high + gb.data.candlesLow[i] + gb.data.candlesClose[i]) / 3);
const atr = gb.data.candlesHigh.slice(1).reduce((acc, high, i) => {
const tr = Math.max(
high - gb.data.candlesLow[i],
Math.abs(high - gb.data.candlesClose[i]),
Math.abs(gb.data.candlesClose[i] - gb.data.candlesLow[i])
);
return acc + tr;
}, 0) / (gb.data.candlesHigh.length - 1);
const upper = tp.map((_, i) => tp.slice(i - period + 1, i + 1).reduce((acc, val) => acc + val, 0) / period + atr * multiplier);
const middle = tp.map((_, i) => tp.slice(i - period + 1, i + 1).reduce((acc, val) => acc + val, 0) / period);
const lower = tp.map((_, i) => tp.slice(i - period + 1, i + 1).reduce((acc, val) => acc + val, 0) / period - atr * multiplier);
return { upper, middle, lower };
};

// calculate On-Balance Volume (OBV)
const calculateOBV = () => {
const obv = [0];
for (let i = 1; i < gb.data.candlesClose.length; i++) {
if (gb.data.candlesClose[i] > gb.data.candlesClose[i - 1]) {
obv.push(obv[i - 1] + gb.data.candlesVolume[i]);
} else if (gb.data.candlesClose[i] < gb.data.candlesClose[i - 1]) {
obv.push(obv[i - 1] - gb.data.candlesVolume[i]);
} else {
obv.push(obv[i - 1]);
}
}
return obv;
};

// calculate indicators
const kc = calculateKC();
const obv = calculateOBV();
const currentObv = obv[obv.length - 1];
const previousObv = obv[obv.length - 2];
const currentClose = gb.data.candlesClose[gb.data.candlesClose.length - 1];
const currentHigh = gb.data.candlesHigh[gb.data.candlesHigh.length - 1];
const currentLow = gb.data.candlesLow[gb.data.candlesLow.length - 1];
const currentKcUpper = kc.upper[kc.upper.length - 1];
const currentKcMiddle = kc.middle[kc.middle.length - 1];
const currentKcLower = kc.lower[kc.lower.length - 1];

// log indicators
console.log(`Current OBV: ${currentObv}`);
console.log(`Previous OBV: ${previousObv}`);
console.log(`Current Close: ${currentClose}`);
console.log(`Current High: ${currentHigh}`);
console.log(`Current Low: ${currentLow}`);
console.log(`Current KC Upper: ${currentKcUpper}`);
console.log(`Current KC Middle: ${currentKcMiddle}`);
console.log(`Current KC Lower: ${currentKcLower}`);

if (enoughTimePassed) {
const buyConditions = currentObv > previousObv && currentClose > currentKcUpper && !gb.data.gotBag;
const sellConditions = currentObv < previousObv && currentClose < currentKcLower && gb.data.gotBag;

// fire orders when conditions are met
if (buyConditions) {
const buyAmount = gb.data.baseBalance / gb.data.ask;
gb.method.buyMarket(buyAmount, gb.data.pairName);
setTimestamp();
} else if (sellConditions) {
gb.method.sellMarket(gb.data.quoteBalance, gb.data.pairName);
setTimestamp();
}
}

// Code is machine generated, review it and run in simulator mode first