API for Custom Indicators The script execution environment is based on a secure JavaScript sandbox. Your script has an execution time limit of 1500ms. The length of the returned data array must not exceed 1500. Therefore, the indicator cannot extend deeper than 1500 candles. You can make up to 8 data source requests within a single indicator. You can make up to 3 HTTP requests (http.get / http.post) within a single run, see "HTTP requests" below. define(title, description, type, isPublic, isPublicCode, options) Registers the indicator in the system. Parameter Type Description title string Indicator name description string Short description type 'onchart' | 'offchart' Indicator placement (on chart or in a separate panel) isPublic true | false Whether the indicator is public isPublicCode true | false Whether the source code is public Example: define("My Indicator", "Shows EMA line", "onchart", true, false); options (sixth argument, optional): { fullRange, warmup }. Calculation range: by default a script computes only the part of the chart that needs it (the bars loaded by scrolling back, or the latest bars on a refresh) plus warmup bars of history before them (default 500) that are used by the calculations but not returned. Declare warmup when the indicator needs more or less history, e.g. { warmup: 200 } for an EMA(200). A script whose values or shapes depend on the WHOLE visible range (VWAP from the first visible candle, cumulative delta, a label at the window maximum, levels drawn across the chart) MUST declare { fullRange: true }, otherwise its results are wrong after loading more history. The mode is read from define() on every save: a script saved without fullRange is calculated partially. define("Window max", "Label at the highest visible high", "onchart", true, true, { fullRange: true }); The input and style classes allow you to create user-configurable settings (similar to input() in PineScript). Method Description number(title, defaultValue, min, max, step) Numeric input range(title, defaultValue, min, max, step) Range input select(title, defaultValue, values) Dropdown selection color(title, defaultValue) Color picker boolean(title, defaultValue) Boolean input Example (input) for main parameters: const length = input.number("Period", 14, 1, 100, 1); const type = input.select("Type", 'sma', ['sma', 'ema']); const color = input.color("Line Color", "#FF0000"); Or (style) for visual parameters: const length = style.number("Size", 14, 1, 100, 1); const color = style.select("Background Color", '#FF0000', ["#FF0000", "#AA0000", "#BB0000"]); const color = style.color("Background Color", "#FF0000"); The source class is used to fetch market data from the server side. ohlcv(), depths(), clusters(), liquidations() and moreData() of the chart timeframe are aligned to the chart candles: every array has one entry per chart candle, depths()[i] is the order book of ohlcv()[i], moreData()[i] holds its derivatives data and so on. When a source has no record for a candle (for example the order book history is shorter than the candle history) the entry is null, so always check for null before reading a record. Data requested with lookback or for another timeframe is returned as is and must be matched by startTime. ohlcv() of the chart market contains only the candles the server has already loaded and never contains null entries. Currently, the length of the returned data array must not exceed 1500. Therefore, the indicator cannot extend deeper than 1500 candles. Currently, 8 data source requests are allowed within a single indicator. The exchange, ticker, and timeframe parameters are by default tied to the current chart where the indicator is opened. The lookback parameter (history of bars from the current date, but no more than 5000) can be set at your discretion. "ohlcv" Method Description ohlcv(exchange, ticker, timeframe, lookback?) Candle data [ { "open": 115886.17, "high": 115911.96, "low": 115764.27, "close": 115815.16, "volume": 228.41528, "startTime": 1757699100000 }, {...}, ... ] "depths" Method Description depths(exchange, ticker, timeframe, lookback?) Limit orders [ { "data": { "asks": { "109425": 5.467, "109495": 3.564, ... }, "bids": { "33000": 3.746, "35000": 16.49, ... } }, "startTime": 1757699100000 }, {...}, ... ] "clusters" Method Description clusters(exchange, ticker, timeframe, lookback?) Cluster data [ { "data": { "totalBuy": 16053385, "totalSell": 20490989, "0.8654": { "buy": 286845, "sell": 336689 }, "0.8653": { "buy": 271848, "sell": 259167 }, "0.8652": { "buy": 318814, "sell": 373497 }, ... }, "startTime": 1757699100000 }, {...}, ... ] "liquidations" Method Description liquidations(exchange, ticker, timeframe, lookback?) Liquidations data [ { "data": { "totalBuy": 16053385, "totalSell": 20490989, "0.8654": { "buy": 286845, "sell": 336689 }, "0.8653": { "buy": 271848, "sell": 259167 }, "0.8652": { "buy": 318814, "sell": 373497 }, ... }, "startTime": 1757699100000 }, {...}, ... ] "moreData" Method Description moreData(exchange, ticker, timeframe, lookback?) Some additional data [ { "data": { "FR": 0.00003777, // Funding Rate "OI": { // Open Interest Candle "open": 85645.708, "close": 83496.225, "high": 85801.04, "low": 83397.918 }, "LS": { "l": 0.5426, "s": 0.4574 }, // Long/Short Ratio "taLS": { "l": 0.552, "s": 0.448 }, // Top Traders Account Ratio "tpLS": { "l": 0.5996, "s": 0.4004 } // Top Traders Position Ratio }, "startTime": 1757699100000 }, {...}, ... ] "orderbook" Method Description orderbook(exchange, ticker) Live orderbook snapshot (the current full book, not tied to candles) { "asks": { "109425": 5.467, "109495": 3.564, ... }, "bids": { "109400": 1.2, "109390": 0.8, ... } } The levels are not sorted, use the ta orderbook helpers (ta.bestBid, ta.sumBids, ...) to work with the book. The historical books of the candles come from depths(): depths()[i].data has the same format, but only the biggest limit orders are stored there. Example: const candles = source.ohlcv(); // The exchange, ticker, and timeframe parameters default to the current chart. const closes = candles.map(c => c.close); All arrays are ordered from oldest candle to newest candle. values[0] is the oldest bar. values[values.length - 1] is the latest bar. HTTP requests The http class sends requests to external HTTP APIs (your own server, a public REST API, an on-chain metrics provider, ...) and returns the response. Like source.*, the calls are written as synchronous functions, no await is needed. Method Description get(url, options?) GET request post(url, body?, options?) POST request. An object or array body is sent as JSON (Content-Type: application/json), a string is sent as is options = { headers: { name: value } }, up to 10 custom headers (for example Authorization or x-api-key). Both methods return the same response object and never throw: { ok: true, // status 200-299 status: 200, // HTTP status, 0 when the request was not sent headers: { "content-type": "application/json", ... }, data: { ... }, // response body parsed as JSON, null when the body is not JSON text: "...", // raw response body error: null // reason when the request failed: "timeout", "url not allowed", "too many requests", ... } Limits and safety: - Up to 3 requests per script run and 60 requests per minute per user. - Only public http:// / https:// URLs. Requests to localhost, private networks and cloud metadata addresses are rejected, redirects are not followed (a 3xx response is returned as is). - Requests are sent from the MobChart proxy servers: the target API sees the proxy address and never the user's IP. - Timeout 5 seconds, request body up to 64 KB, response up to 512 KB (bigger responses fail with "response too large"). - Host, Cookie, Proxy-*, X-Forwarded-* and other connection headers can not be set, Set-Cookie is never returned. - Alert checks run the script on the server regularly: the API is called on every check, keep the rate limits of the target API in mind. - The code of a public indicator (isPublicCode = true) is visible to everyone: never put API keys into public code. Possible error values: invalid url, url not allowed, invalid headers, invalid body, body too large, response too large, timeout, too many requests, rate limit, busy, proxy unavailable, network error. Example (GET): define("Fear & Greed", "Crypto Fear & Greed index", "offchart", true, true); const res = http.get("https://api.alternative.me/fng/?limit=30"); if (!res.ok) logger.log("request failed:", res.status, res.error); // res.data = { data: [{ value: "71", timestamp: "1757635200" }, ...] } const byDay = {}; (res.data?.data || []).forEach((row) => { byDay[Number(row.timestamp) * 1000] = Number(row.value); }); const candles = source.ohlcv(); const values = candles.map((c) => c ? byDay[c.startTime - (c.startTime % 86400000)] ?? null : null); plot.lineSeries("Fear & Greed", values, "#00C8FF"); Example (POST): const candles = source.ohlcv(); const last = candles.at(-1); const res = http.post( "https://example.com/api/signals", { exchange: currentExchange, ticker: currentTicker, close: last.close }, { headers: { "x-api-key": "YOUR_KEY" } } // private code only ); logger.log("signal sent:", res.ok, res.status, res.data); Global Variables Variable Description Example currentExchange Current exchange on the chart binance, binanceFutures, coinbase, ... currentTicker Current ticker on the chart (format depends on the exchange) BTCUSDT, ADA-USD, BTC/USDC, ... currentTimeframe Current timeframe on the chart 1m, 5m, 15m, ... Example: const candles = source.ohlcv(currentExchange, currentTicker, currentTimeframe); const closes = candles.map(c => c.close); Logging The logger class is used for outputting information and debugging. Method Description log(...args) Adds an entry to the log (with limits on length and quantity) Example: const candles = source.ohlcv(); logger.log("First candle:", candles?.[0]); On the chart the log is shown in the script's log panel. Through MCP the script_run tool executes a script on real candles and returns every logger.log(...) line together with runtime errors, so an agent can test and debug a script before saving it. Visualization The plot class is responsible for drawing graphical elements and data series. Geometric Shapes Method Description box(title, date1, price1, date2, price2, fill, opacity, stroke, strokeOpacity, strokeWidth) Rectangle square(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) Square diamond(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) Diamond triangle(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) Triangle circle(title, date1, price1, radius = 10, fill, opacity, stroke, strokeOpacity, strokeWidth) Circle text(title, value, date1, price1, fontSize, fill, opacity) Text on chart straightLine(title, date1, price1, date2, price2, stroke, strokeOpacity, strokeWidth, strokeDasharray) Straight line horizontalLine(title, price1, stroke, strokeOpacity, strokeWidth, strokeDasharray) Horizontal line across the whole chart verticalLine(title, date1, stroke, strokeOpacity, strokeWidth, strokeDasharray) Vertical line across the whole chart Parameter Data Type Description title string Element name (unique identifier on the chart) date1 number (timestamp, ms) Starting timestamp price1 number Starting price (Y-axis coordinate) date2 number (timestamp, ms) Ending timestamp (for lines and rectangles) price2 number Ending price (for lines and rectangles) size number Shape size (in pixels) radius number Circle radius (default 10) fill string (hex, e.g., "#FF0000") Fill color of the shape opacity number (0–1) Fill opacity stroke string (hex, e.g., "#000000") Stroke color strokeOpacity number (0–1) Stroke opacity strokeWidth number Stroke width (in pixels) strokeDasharray array (e.g., [5,5]) Dashed line style fontSize number Text size (in pixels) value string Text value (for text method) Example: plot.text("Text object", "Hello world!", 1757699100000, 115000, 10, "#00C8FF", 0.5); plot.straightLine(`${namePrefix} Open`, level.timeStart, level.open, level.timeEnd, level.open, color); plot.circle(`${namePrefix} High`, level.timeStart, level.high, 3, color); No more than 3000 shapes can be displayed on the chart. Data Series Method Description lineSeries(title, values, stroke, strokeOpacity, strokeWidth, strokeDasharray) Line chart barSeries(title, values, fill, opacity) Bar chart candleSeries(title, openValues, closeValues, highValues, lowValues, fill, opacity) Candles areaSeries(title, values, fill, opacity, stroke, strokeOpacity, strokeWidth, base) Filled area between the line and base (default: the bottom of the chart) bandSeries(title, upperValues, lowerValues, fill, opacity, stroke, strokeOpacity, strokeWidth, step) Filled band between two series, every candle keeps its own color and opacity Parameter Data Type Description values, upperValues, lowerValues (number | null)[] One value per candle, null hides the candle fill, stroke string (hex) or function Color, or a function returning the color of the candle: (value, i) for lineSeries, barSeries, candleSeries and areaSeries, (upper, lower, i) for bandSeries opacity number (0-1) or function Fill opacity, a function (value, i) / (upper, lower, i) for areaSeries / bandSeries base number | null areaSeries base level, null = the bottom of the chart step boolean bandSeries style: true = one column per candle (default), false = a continuous polygon Example: plot.lineSeries("EMA", ta.ema(closes, 20), "#00C8FF"); // every candle keeps its own color: green where the close is above the EMA, red below it plot.lineSeries("Close", closes, (v, i) => v > ema[i] ? colorBuy : colorSell); plot.barSeries("Premium %", premiumPercents, ((d) => d > 0 ? color1 : color2), 1); plot.candleSeries( "Candles", opens, closes, highs, lows, (v, i) => { return opens?.[i] > closes?.[i] ? colorBuy : colorSell } ); plot.areaSeries("Volume", volumes, "#00C8FF", 0.3); // area colored per candle, more opaque on the bigger candles plot.areaSeries("Delta", deltas, (v, i) => v >= 0 ? colorBuy : colorSell, (v, i) => Math.min(1, Math.abs(v) / maxDelta)); // pressure band between the close and 1% below it, colored and shaded by the imbalance ratio of the candle plot.bandSeries( "Bid pressure", closes, closes.map(c => c * 0.99), (upper, lower, i) => ratios[i] > 0 ? bidColor : askColor, (upper, lower, i) => Math.abs(ratios[i]) / 100 ); No more than 20 data series can be displayed on the chart. Technical Analysis The ta class implements the PineScript v5 ta.* functions. Pine works bar by bar, here every function takes whole series (arrays, one value per candle, oldest first) and returns a series of the same length that can be plotted directly. Values are null while the period is not filled yet, and null inputs propagate like Pine's na. Pine functions that read high, low, close or volume implicitly take those arrays as arguments. Functions returning several series (bb, kc, macd, stoch, dmi, supertrend) return an array that also has named properties: const [middle, upper, lower] = ta.bb(closes, 20, 2) or ta.bb(closes, 20, 2).upper. Helper Functions Method Description highest(values, length) / lowest(values, length) Highest / lowest value over length bars highestbars(values, length) / lowestbars(values, length) Offset (0 or negative) to the highest / lowest bar max(values) / min(values) All-time high / low up to each bar range(values, length) highest - lowest change(values, length = 1) values[i] - values[i - length] mom(values, length) Momentum roc(values, length) Rate of change, % cum(values) Running total rising(values, length) / falling(values, length) Value is above / below each of the previous length values variance(values, length, biased = true) Variance stdev(values, length, biased = true) Standard deviation dev(values, length) Mean absolute deviation median(values, length) Median mode(values, length) Most frequent value (the smallest one on a tie) percentile_linear_interpolation(values, length, percentage) Percentile, linear interpolation between the two nearest ranks percentile_nearest_rank(values, length, percentage) Percentile, nearest rank method percentrank(values, length) % of the previous length values less than or equal to the current one correlation(series1, series2, length) Correlation coefficient crossover(series1, series2) Upward crossover (either argument can be a number: ta.crossover(rsi, 70)) crossunder(series1, series2) Downward crossover cross(series1, series2) Any crossover barssince(condition) Bars since condition (array of booleans) was last true valuewhen(condition, values, occurrence = 0) Value of values when condition was true occurrence times ago pivothigh(values, leftbars, rightbars) / pivotlow(...) Pivot value on the confirming bar, the pivot itself is rightbars bars earlier Moving Averages Method Description sma(values, length) Simple moving average ema(values, length) Exponential moving average rma(values, length) Wilder's moving average (used by RSI / ATR) wma(values, length) Weighted moving average hma(values, length) Hull moving average alma(values, length, offset = 0.85, sigma = 6, floor = false) Arnaud Legoux moving average swma(values) Symmetrically weighted moving average over 4 bars vwma(values, volumes, length) Volume weighted moving average linreg(values, length, offset = 0) Linear regression curve Oscillators Method Description rsi(values, length) Relative Strength Index cmo(values, length) Chande Momentum Oscillator cog(values, length) Center of Gravity mfi(values, volumes, length) Money Flow Index (values is usually hlc3) tsi(values, shortLength, longLength) True Strength Index, -1..1 wpr(highs, lows, closes, length) Williams %R stoch(highs, lows, closes, period = 14, smoothK = 3, smoothD = 3) Stochastic [k, d], smoothK = 1 gives the raw value cci(values, length) / cci(highs, lows, closes, length) Commodity Channel Index (of the typical price for the second form) macd(values, fast = 12, slow = 26, signalPeriod = 9) MACD [macd, signal, histogram] Bands and Channels Method Description bb(values, length = 20, mult = 2) / bollinger(...) Bollinger Bands [middle, upper, lower] bbw(values, length, mult) Bollinger Bands width (upper - lower) / middle kc(values, highs, lows, closes, length = 20, mult = 2, useTrueRange = true) Keltner Channels [middle, upper, lower] kcw(values, highs, lows, closes, length, mult, useTrueRange) Keltner Channels width (upper - lower) / middle Trend and Volatility Method Description tr(highs, lows, closes, handleNa = false) True range (handleNa = true gives high - low on the first bar) atr(highs, lows, closes, period = 14) Average True Range dmi(highs, lows, closes, diLength = 14, adxSmoothing = 14) Directional Movement [plus, minus, adx] adx(highs, lows, closes, period = 14) Average Directional Index sar(highs, lows, closes, start = 0.02, inc = 0.02, max = 0.2) Parabolic SAR supertrend(highs, lows, closes, factor = 3, atrPeriod = 10) Supertrend [supertrend, direction], direction -1 = up trend, 1 = down trend Volume Method Description vwap(values, volumes, anchor?) / vwap(highs, lows, closes, volumes, anchor?) Cumulative VWAP (of the typical price for the second form), restarts on bars where anchor (array of booleans) is true obv(closes, volumes) On Balance Volume accdist(highs, lows, closes, volumes) Accumulation / Distribution iii(highs, lows, closes, volumes) Intraday Intensity Index pvt(closes, volumes) Price-Volume Trend nvi(closes, volumes) / pvi(closes, volumes) Negative / Positive Volume Index wad(highs, lows, closes) Williams Accumulation / Distribution wvad(opens, highs, lows, closes, volumes) Williams Variable Accumulation / Distribution Orderbook Helpers orderbook is source.orderbook() or source.depths()[i].data: {asks: {price: quantity}, bids: {price: quantity}} Method Description orderbookSide(orderbook, side) One side ('bids' / 'asks') as [[price, quantity], ...] sorted best price first bestBid(orderbook) / bestAsk(orderbook) Best bid / ask price (null for an empty book) sumBids(orderbook, percent, price, inQuote) Cumulative bid quantity between price (default: best bid) and price * (1 - percent / 100), inQuote = true sums price * quantity sumAsks(orderbook, percent, price, inQuote) Cumulative ask quantity between price (default: best ask) and price * (1 + percent / 100) Example: const candles = source.ohlcv(); const closes = candles.map(c => c.close); const rsiValues = ta.rsi(closes, 14); plot.lineSeries("RSI", rsiValues, "#00C8FF"); Alerts Users can create alerts for an indicator from the Alerts tab in the indicator settings on the chart. Alerts are checked on the server side regularly, notifications are sent to Telegram and/or a user defined Webhook URL. Alert Settings - alertInput The alertInput class declares alert settings. It works exactly like input, but the fields are shown in the Alerts tab and every user configures their own values for their alert. Scripts without alertInput declarations can not be used for alerts. Method Description number(title, defaultValue, min, max, step) Numeric input range(title, defaultValue, min, max, step) Range input select(title, defaultValue, values) Dropdown selection color(title, defaultValue) Color picker boolean(title, defaultValue) Boolean input Example: const alertVolumeThreshold = alertInput.number("Volume threshold", 1000, 0, 10000000, 1); Firing Alerts - alert.fire Method Description fire(title, message, date) Fires an alert notification - All three parameters are REQUIRED. A call with a missing title, message or date is skipped (a warning is written to the log). - Keep the alert title constant, do not generate unique titles. Each title fires only once per run (max 20 titles per run). - date is the candle startTime the signal belongs to: the same title notifies again only when it fires for a NEWER candle. Signals from candles older than the moment the user created their alert are ignored (anti-spam). Example: if (candle.volume > alertVolumeThreshold) { alert.fire("High volume", `vol ${candle.volume}`, candle.startTime); } Shape Alerts - plot.(...).alert(...) All geometric shapes (box, square, diamond, triangle, circle, straightLine, horizontalLine, verticalLine, text) return a handle with an .alert() method. The alert fires with the shape's date1 automatically, so the anti-spam logic works without extra code. This is the recommended way for per-candle signals. Two forms: // simple: fires every time the shape is plotted for a new date plot.box(...).alert("High volume", `vol ${candle.volume}`); // callback: return nothing to skip, [title, message] to fire plot.box(...).alert((shape) => { if (candle.volume > alertVolumeThreshold) return ["High volume", `vol ${candle.volume}`]; }); The callback receives the shape data (date1, price1, date2, price2, ...). Series Alerts - plot.(...).alert(...) Data series (lineSeries, barSeries, candleSeries, areaSeries, bandSeries) also return the .alert() handle. The callback receives the LATEST data point and the latest candle date, the alert fires with that date. - lineSeries / barSeries / areaSeries: the callback receives the last value (a number) - candleSeries: the callback receives the last candle {open, close, high, low} - bandSeries: the callback receives the last {upper, lower} plot.lineSeries("RSI", rsiValues, color).alert((rsi, date) => { if (rsi > alertRsiThreshold) return ["RSI high", `RSI ${rsi.toFixed(1)}`]; }); A condition that stays true notifies once per NEW candle. For one-time signals use crossover-style conditions (ta.crossover). Full alerts example: const title = "Volume alerts"; const description = "Shows boxes on high-volume candles"; define(title, description, "onchart", true, true); const color = style.color("Color", "#00C8FF"); const volumeThreshold = input.number("Volume threshold", 1000, 0, 10000000, 1); const alertVolumeThreshold = alertInput.number("Alert volume threshold", 5000, 0, 10000000, 1); const alertCloseThreshold = alertInput.number("Alert close threshold", 100000, 0, 10000000, 1); const candles = source.ohlcv(); const closes = candles.map(c => c.close); // series alert: checks the latest close plot.lineSeries("Closes", closes, color).alert((close, date) => { if (close > alertCloseThreshold) return ["Close above threshold", `close ${close}`]; }); for (let i = 0; i < candles.length; i++) { const candle = candles[i]; if (candle.volume > volumeThreshold) { const right = i < candles.length - 1 ? candles[i + 1].startTime : candle.startTime + 60000; // shape alert: checked against the user's alert threshold plot.box(`Volume ${i}`, candle.startTime, candle.high, right, candle.low, "#00C8FF", 0.3) .alert(() => { if (candle.volume > alertVolumeThreshold) return ["High volume", `vol ${candle.volume}`]; }); } } Examples of indicators const title = "Your first indicator"; const description = "Shows closes lines on the chart"; const indicatorType = "onchart"; define(title, description, type, true, true); const color = input.color("Line Color", "#00C8FF"); const candles = source.ohlcv(); const closes = candles.map(c => c.close); plot.lineSeries("Closes", closes, color); define("Example Indicator", "Demonstrates basic plotting", "onchart", true, true); const color = input.color("Line Color", "#00FF99"); const candles = source.ohlcv(); const closes = candles.map(c => c.close); const ema20 = ta.ema(closes, 20); plot.lineSeries("EMA 20", ema20, color); const title = "Colored Volume Bars"; const description = "Volumes colored based on candle direction."; const indicatorType = "onchart"; const isPublic = true; const isPublicCode = true; define(title, description, indicatorType, isPublic, isPublicCode); // Get OHLC candles const candles = source.ohlcv(); const colorUp = style.color("Color 1", "#2BB462"); // Greenish color for rising prices const colorDown = style.color("Color 2", "#FB4C51"); // Reddish color for falling prices const volumes = candles.map(candle => candle.volume); plot.barSeries("Volume", volumes, (value, index) => { const currentCandle = candles[index]; return currentCandle.close >= currentCandle.open ? colorUp : colorDown; }); define('Coinbase Premium Index', 'Coinbase Premium Index', 'offchart', true, false); const color1 = style.color("Color 1", "#2BB462"); const color2 = style.color("Color 2", "#FB4C51"); // Get OHLC candles const cbCandles = source.ohlcv('coinbase', 'BTC-USD'); const binanceCandles = source.ohlcv('binance', 'BTCUSDT'); // Extract closes const cbCloses = cbCandles.map(c => c.close); const binanceCloses = binanceCandles.map(c => c.close); // --- Coinbase Premium Index calculation --- const premiumPercent = cbCloses.map((cb, i) => { const b = binanceCloses[i]; return ((cb - b) / b) * 100; }); // Plot premium index plot.barSeries("Premium %", premiumPercent, ((d) => d > 0 ? color1 : color2)) // Percentage difference const title = "Candles"; const description = "Simple candles draw"; const indicatorType = "offchart"; const isPublic = true; const isPublicCode = true; define(title, description, indicatorType, isPublic, isPublicCode); const colorBuy = style.color("Buy Color", "#2BB462"); const colorSell = style.color("Sell Color", "#7a3d3f"); const candles = source.ohlcv(); const closes = candles.map(c => c.close); const opens = candles.map(c => c.open); const highs = candles.map(c => c.high); const lows = candles.map(c => c.low); plot.candleSeries( "Candles", opens, closes, highs, lows, (v, i) => { return opens?.[i] > closes?.[i] ? colorBuy : colorSell } ); Order Book Pressure (bid / ask depth imbalance bands around the price): const title = "Order Book Pressure"; const description = "Bid / ask depth imbalance bands around the price"; define(title, description, "onchart", true, true); const minBidRatio = input.number("Min bid ratio %", 20, 0, 100, 1); const minAskRatio = input.number("Min ask ratio %", 20, 0, 100, 1); const depthFrom = input.select("Depth from", "0%", ["0%", "1%", "2.5%", "5%"]); const depthTo = input.select("Depth to", "5%", ["1%", "2.5%", "5%", "10%"]); const bidColor = style.color("Bid fill color", "#a5d6a7"); const askColor = style.color("Ask fill color", "#f1627e"); const extremeColor = style.color("Extreme color", "#ffff00"); const highlight = style.boolean("Highlight extremes", true); const showRef = style.boolean("Show reference lines", true); const alertMinRatio = alertInput.number("Alert min |ratio| %", 40, 0, 100, 1); const bounds = [1, 2.5, 5, 10]; // depth bands in % from the close const from = parseFloat(depthFrom); const to = parseFloat(depthTo); const candles = source.ohlcv(); const depths = source.depths(); const liveBook = source.orderbook(); // orderbook of every candle: depths()[i] is the snapshot of candles[i] (the biggest limit orders), the latest candle uses the live book const lastIndex = candles.length - 1; const books = candles.map((c, i) => { if (!c) return null; if (i === lastIndex && ta.bestBid(liveBook)) return liveBook; return depths[i]?.data || null; }); const closes = candles.map(c => c ? c.close : null); // imbalance ratio of every band in % (positive = bids dominate, negative = asks dominate) const ratios = bounds.map((upper, b) => { const lower = b === 0 ? 0 : bounds[b - 1]; return candles.map((c, i) => { const book = books[i]; if (!c || !book) return null; const bid = ta.sumBids(book, upper, c.close) - ta.sumBids(book, lower, c.close); const ask = ta.sumAsks(book, upper, c.close) - ta.sumAsks(book, lower, c.close); const total = bid + ask; return total > 0 ? ((bid - ask) / total) * 100 : 0; }); }); // a stronger ratio -> a more opaque band, extreme ratios get the extreme color const bandOpacity = (ratio) => Math.min(0.95, Math.abs(ratio || 0) / 70 * 0.95); const bandColor = (ratio) => highlight && Math.abs(ratio || 0) >= 63 ? extremeColor : (ratio >= 0 ? bidColor : askColor); bounds.forEach((upper, b) => { const lower = b === 0 ? 0 : bounds[b - 1]; if (from > lower || upper > to) return; // the band is outside of the selected depth range const bidUpper = closes.map(c => c != null ? c * (1 - lower / 100) : null); const bidLower = closes.map(c => c != null ? c * (1 - upper / 100) : null); const askLower = closes.map(c => c != null ? c * (1 + lower / 100) : null); const askUpper = closes.map(c => c != null ? c * (1 + upper / 100) : null); if (showRef) { plot.lineSeries(`Bid ${upper}%`, bidLower, bidColor, 0.4); plot.lineSeries(`Ask ${upper}%`, askUpper, askColor, 0.4); } // bid pressure: the band is filled only on the candles where bids dominate by more than the min ratio plot.bandSeries( `Bid pressure ${lower}-${upper}%`, bidUpper.map((v, i) => ratios[b][i] >= minBidRatio ? v : null), bidLower, (upper, lower, i) => bandColor(ratios[b][i]), (upper, lower, i) => bandOpacity(ratios[b][i]) ); // ask pressure plot.bandSeries( `Ask pressure ${lower}-${upper}%`, askUpper.map((v, i) => ratios[b][i] <= -minAskRatio ? v : null), askLower, (upper, lower, i) => bandColor(ratios[b][i]), (upper, lower, i) => bandOpacity(ratios[b][i]) ); // alert on the latest candle const latestRatio = ratios[b][lastIndex]; if (latestRatio != null && Math.abs(latestRatio) >= alertMinRatio) { alert.fire(`Pressure ${lower}-${upper}%`, `ratio ${latestRatio.toFixed(1)}%`, candles[lastIndex].startTime); } }); The script is executed once. Execution order: 1. define() 2. input/style initialization 3. source requests 4. calculations 5. plotting - Never invent new functions. - Use only documented API. - If a function does not exist, explain that it is unavailable. - Always return complete code. - Preserve array lengths. - Use map/filter/reduce when processing arrays. - Do not exceed 8 source requests. - Do not exceed 20 plotted series. - Do not exceed 3000 shapes. - Do not write PineScript. - Do not write TradingView code.