DEV Community

NydarTrading
NydarTrading

Posted on • Originally published at nydar.co.uk

Why We Built a Pine Script Indicator Engine (And What Traders Are Doing With It)

We ship 105 technical indicators out of the box. RSI, MACD, Bollinger Bands, ADX, ATR, Stochastic, Ichimoku — the full catalogue. For most traders, that's more than enough. You could build a solid strategy using three of them and never touch the rest.

But the traders who stick around — the ones who come back every day, who build real edge, who actually make money — they always end up wanting something we haven't built yet.

One wanted a triple-confirmation oversold indicator that only fires when RSI, Bollinger Band touch, and volume spike happen simultaneously. Another wanted a custom momentum oscillator that weights recent price action differently during high-volatility regimes. A third wanted di_plus and di_minus with custom smoothing periods that don't match any standard ADX implementation.

A forex trader wanted a session-aware VWAP that resets at the London open rather than midnight UTC. A crypto trader wanted an indicator that tracks the spread between spot price and perpetual futures funding rate — something that doesn't exist in any standard library because it requires combining two different data feeds.

We kept getting the same request in different forms: "Can I build my own indicator?"

So we built an engine that lets you do exactly that.

The problem with "just add more indicators"

The obvious solution to "traders want more indicators" is to keep adding them to the platform. And for a while, that's what we did. Every time someone asked for something we didn't have, we'd evaluate whether it was worth adding to the core library.

This doesn't scale. Here's why.

Every indicator we add becomes something we have to maintain. It needs documentation. It needs to handle edge cases across crypto, stocks, and forex — and those asset classes behave differently in ways that affect calculations. A volume-based indicator that works perfectly on stock data breaks on forex pairs where "volume" means tick count rather than actual shares traded. Our 105 built-in indicators represent a significant maintenance surface, and each one had to be tested against all three asset classes before we shipped it.

More importantly, the requests are infinitely varied. Traders don't just want standard indicators — they want combinations of indicators with custom logic layered on top. They want moving averages that switch between SMA and EMA based on volatility. They want RSI that uses a different lookback period during trending versus ranging markets. They want things that don't have names yet because they invented them.

That last point is worth emphasising. Some of the most effective indicators we've seen aren't published in any textbook. They're the product of a trader spending months watching a specific market, noticing a pattern, and codifying it. The trader who watches the BTC/USDT order book all day and realises that a specific ratio of bid-to-ask volume at certain price levels precedes a move — that's not something we can anticipate. That insight belongs to the trader. They just need a way to express it.

You can't pre-build infinity. You have to give people a language.

Why Pine Script syntax

When we decided to build a custom indicator engine, the first decision was the scripting language. We had three realistic options:

JavaScript. We're a web platform, so this was the obvious choice from a technical standpoint. The browser already has a JS runtime. But giving users access to a full JavaScript runtime inside a trading chart is a security nightmare. Even sandboxed, the attack surface is enormous — you'd need to block network access, filesystem access, infinite loops, memory bombs, and a hundred other vectors. And JS is verbose for numerical computation. Nobody wants to write Array.from({length: period}, (_, i) => close[i]).reduce((a, b) => a + b, 0) / period when they mean "average of last N closes."

A custom DSL. We could design our own language from scratch, tailored exactly to our needs. Clean, safe, optimised for time-series data. But nobody would know how to write it. The learning curve would kill adoption before it started. Every user would need to learn a new language just to do something they might already know how to express. We'd also need to write all the documentation, tutorials, and examples from scratch — a massive investment with no guarantee anyone would bother reading it.

Pine Script syntax. And this is what we chose. Not because we're trying to clone any particular platform, but because Pine Script has become the de facto standard for expressing trading logic. Thousands of tutorials exist online. Every trading forum has Pine Script examples. When a trader googles "RSI divergence indicator code" or "di_plus pine script," the results are overwhelmingly Pine Script.

By adopting Pine Script syntax, we inherited an entire ecosystem of knowledge. Traders can take code they've written elsewhere, paste it into our custom indicator editor, make minor adjustments, and have it running on live data in minutes. They don't need to learn anything new. The syntax they already know just works.

There's a pragmatic angle too. Pine Script is purpose-built for time-series computation on OHLCV data. The language assumes you're working with price bars. Variables implicitly reference the current bar. Built-in functions like ta.sma() and ta.rsi() handle the lookback windowing automatically. This makes trading logic dramatically more concise than equivalent code in a general-purpose language, which means fewer bugs and faster iteration for the trader.

What the engine actually does

Let me walk through what happens when you write a custom indicator on Nydar. This isn't a tutorial — we have a comprehensive Pine Script guide and detailed custom indicators documentation for that — but understanding the architecture explains why it works the way it does.

When you write indicator code, it goes through three stages:

1. Parsing and validation. Your code is parsed into an abstract syntax tree. We check for syntax errors, undefined variables, type mismatches, and potentially dangerous operations. This is where we catch mistakes before they hit your chart. Unlike a general-purpose language, we can validate that your code will produce a plottable result — it must output at least one series of values. We also enforce resource limits at this stage: no unbounded loops, no recursive function calls deeper than a set threshold, no allocations above a memory ceiling. This is how we keep the engine safe without needing a full sandbox.

2. Compilation to an execution plan. The parsed code is compiled into a series of operations that our engine can execute efficiently. This is where Pine Script's design really helps us. Because the language is inherently bar-by-bar — every statement implicitly iterates over the historical price data — we can optimise the execution path in ways that wouldn't be possible with arbitrary code. Common subexpressions get deduplicated. If your indicator computes ta.sma(close, 20) in three different places, we calculate it once. Built-in function calls get routed to our optimised implementations rather than being interpreted line by line.

3. Execution against price data. The execution plan runs against the current chart's OHLCV data. Results are cached and only recomputed when new bars arrive or parameters change. This is critical for real-time performance — when a new tick comes in, we don't recalculate the entire history. We incrementally update only the current bar and any values that depend on it. On a chart showing a year of 5-minute data, that's the difference between recalculating 100,000+ bars per tick versus updating one.

The result is an indicator that feels native. It updates in real time, it works across all timeframes, and it scales to whatever history depth the chart is showing. A user shouldn't be able to tell the difference between a built-in indicator and a custom one — that was our design goal, and we hit it.

Real examples from our users

The best part of building a custom indicator engine is seeing what people do with it that you never anticipated. Here are some of the more interesting indicators traders have built:

Triple-confirmation oversold signal

This was the original request that pushed us to build the engine. The logic is straightforward — only signal when three independent conditions agree:

//@version=5
indicator("Triple Oversold", overlay=true)
rsiOversold = ta.rsi(close, 14) < 30
bbTouch = close <= ta.sma(close, 20) - 2 * ta.stdev(close, 20)
volSpike = volume > 2 * ta.sma(volume, 20)
signal = rsiOversold and bbTouch and volSpike
plotshape(signal, style=shape.triangleup, location=location.belowbar, color=color.green, size=size.normal)
Enter fullscreen mode Exit fullscreen mode

It fires rarely — maybe once or twice a month on any given stock — but when it does, the setup has a high probability of at least a short-term bounce. The trader who built this told us it was more valuable than any single built-in indicator because it eliminated the manual work of checking three separate panels and trying to eyeball whether all three conditions were true at the same bar. That's the kind of thing humans are bad at and computers are good at.

Volatility-adaptive moving average

A forex trader built a moving average that automatically adjusts its period based on ATR:

//@version=5
indicator("Adaptive MA", overlay=true)
atrVal = ta.atr(14)
atrMean = ta.sma(atrVal, 100)
ratio = atrVal / atrMean
adaptPeriod = math.round(20 * ratio)
adaptPeriod := math.max(5, math.min(adaptPeriod, 50))
adaptMA = ta.sma(close, adaptPeriod)
plot(adaptMA, color=color.blue, linewidth=2)
Enter fullscreen mode Exit fullscreen mode

During low-volatility consolidation, the ratio drops below 1 and the period shortens — making the MA more responsive to breakouts. During high-volatility trends, the ratio rises and the period extends — filtering out noise and keeping you in the trade. The result is a single line that adapts to market conditions instead of requiring the trader to manually switch between fast and slow MAs depending on what the market is doing.

Custom ADX with di_plus / di_minus smoothing

This is the one that shows up in our search console data. A lot of traders want the directional movement components (di_plus, di_minus) from the ADX calculation, but with non-standard smoothing:

//@version=5
indicator("Custom DI", overlay=false)
period = input.int(7, "DI Period")
smooth = input.int(5, "Smoothing")
upMove = high - high[1]
downMove = low[1] - low
plusDM = upMove > downMove and upMove > 0 ? upMove : 0
minusDM = downMove > upMove and downMove > 0 ? downMove : 0
atrVal = ta.ema(ta.tr, period)
diPlus = 100 * ta.ema(plusDM, period) / atrVal
diMinus = 100 * ta.ema(minusDM, period) / atrVal
plot(ta.ema(diPlus, smooth), "DI+", color=color.green)
plot(ta.ema(diMinus, smooth), "DI-", color=color.red)
Enter fullscreen mode Exit fullscreen mode

The default 14-period Wilder smoothing in the standard ADX is tuned for daily charts. If you're trading 5-minute bars, that 14-period lookback covers just over an hour — not enough context for some strategies, too much for others. This version lets you set both the DI period and an additional smoothing layer independently. Several of our users run this on scalping setups where the standard ADX is too laggy.

Multi-timeframe divergence scanner

One of the more ambitious builds — a user created an indicator that checks for RSI divergence across three timeframes simultaneously. Price making higher highs while RSI makes lower highs on the 15-minute, 1-hour, and 4-hour charts at the same time. When all three align, it flags a high-probability reversal setup.

This required accessing multiple timeframe data within a single indicator, which was one of the features we had to add to the engine after launch. The request.security() function lets you pull data from a different timeframe than the one the chart is displaying. It's one of those features where the first user who asked for it made us realise we'd shipped the engine incomplete — of course you need multi-timeframe access. Trading decisions are almost never made on a single timeframe.

The pitfalls we help you avoid

Building an indicator engine also means watching people make mistakes, and then building guardrails. The three most common issues in custom indicator development are well-documented in technical analysis literature, but they're easy to fall into when you're writing code:

Repainting. An indicator "repaints" when it changes its historical values as new data arrives. This is the most dangerous pitfall because it makes backtesting results look better than they are. If your indicator uses close on the current bar and the bar hasn't finished yet, every tick changes that value — and when you look at the historical chart later, you only see the final values. It looks like the indicator was right all along, but in real time it was flickering between states. Our engine flags common repainting patterns during the validation stage and warns you before the indicator runs.

Overfitting. This is the custom indicator equivalent of curve-fitting a backtest. You tune 6 parameters until your indicator perfectly identifies every reversal in the last year of data, and then it fails completely on new data because you've modelled the noise rather than the signal. We don't have a magic solution for this — it's fundamentally a statistical problem — but we do show parameter sensitivity warnings. If changing a parameter by 1 dramatically changes the number of signals, that's a red flag.

Lookahead bias. Using future data in calculations, usually accidentally. The classic example is referencing close in a condition that's supposed to fire at the open — the close price isn't known at the open, so the indicator "works" on historical data but can't work in real time. Pine Script's bar-by-bar execution model helps prevent this by default, since each statement only has access to the current and previous bars. But it's still possible to introduce lookahead through creative use of request.security() with a lower timeframe, and we check for those patterns.

We built detection for these three issues because they represent the gap between "my indicator looks brilliant on a historical chart" and "my indicator actually works for live trading." Bridging that gap is the difference between a toy and a tool.

What we learned building it

Building the engine taught us some things that surprised us.

Most users start by copying, not creating. We expected people to write indicators from scratch. In reality, about 80% of custom indicators on the platform started as code copied from a forum, blog, or tutorial. Users paste it in, see if it works, then start tweaking parameters. This is why Pine Script compatibility was so important — it dramatically lowered the friction from "I found some code online" to "it's running on my chart." The learning journey isn't blank-page-to-finished-indicator. It's copy, tweak, understand, eventually create.

Error messages matter more than features. The single biggest factor in user retention with custom indicators wasn't how powerful the engine was. It was how helpful the error messages were. When we launched, errors were technical — "unexpected token at line 14." Users bounced. When we rewrote them to be contextual — "Line 14: ta.sma expects a number for the period parameter, but got a string. Did you mean to use input.int(14) instead?" — completion rates doubled. We now treat error message quality as a first-class feature, not an afterthought.

Performance is a feature. Early versions of the engine recalculated everything on every tick. On a 5-minute chart with a year of data, that's over 100,000 bars being processed multiple times per second. The UI would freeze, the chart would stutter, and users would assume the indicator was broken rather than slow. We had to build the incremental update system described above, and it made the difference between "cool tech demo" and "tool I actually use for trading." Nobody cares how clever your architecture is if the chart lags.

Saving and sharing drives adoption. When we added the ability to save indicators and load them across different charts, usage tripled. People build an indicator once and then use it everywhere — on every asset, every timeframe, every layout. The indicator becomes part of their workflow rather than a one-off experiment. We're considering adding community sharing next, though we want to get the security review right before we let users run each other's code. The risk of a cleverly crafted indicator that looks helpful but actually misleads is real, and we'd rather launch it right than launch it fast.

The 105 built-in indicators got better too. An unexpected benefit: building the custom engine forced us to re-examine our built-in indicator implementations. When users can write their own RSI and compare it to ours, any discrepancy gets noticed immediately. We found and fixed three edge-case bugs in our built-in indicators that had been there since launch — all discovered because a user's custom version produced slightly different values and asked us why. Competition from your own users turns out to be excellent quality assurance.

Where this is going

Custom indicators are one piece of a larger vision. If you can express your trading logic as code, you should be able to do more than just visualise it on a chart.

We're working on connecting custom indicators to our backtesting engine. Write your indicator, define entry and exit rules, and test it against historical data — all without leaving the platform. The indicator engine already computes values across full history, so the data pipeline is there. We just need to build the strategy wrapper and the results dashboard. The goal is to go from "I have an idea" to "here's how it would have performed over the last 2 years" in under a minute.

Beyond backtesting, we want custom indicators to feed into our alert system. "Notify me when my triple-confirmation oversold signal fires on any stock in my watchlist." That's the point where custom indicators become truly operational — not just a visual tool on a chart you're staring at, but an automated scanner that watches the market for you while you do other things. Combined with our existing Telegram notification pipeline, your custom indicator could wake you up at 3am when that crypto setup you've been waiting for finally appears.

The endgame is a platform where your entire trading workflow — from idea to indicator to backtest to live alerts to execution — lives in one place. We're not there yet. But the custom indicator engine is the foundation that everything else builds on, because it's where your trading logic gets expressed in a form that a computer can work with.

We built Nydar because we believe traders deserve professional-grade tools without the professional-grade price tag. The custom indicator engine is the purest expression of that philosophy. Instead of deciding what indicators you need, we gave you the building blocks to create exactly what you need.

If you've been sitting on an indicator idea, give it a try. And if you get stuck, the Pine Script guide and glossary are there to help you get unstuck.

Your edge is your own. We just built the engine to run it.


Originally published at Nydar. Nydar is a free trading platform with AI-powered signals and analysis.

Top comments (0)