Regression_ToolkitThis is toolkit/library bridges advanced regression approaches not natively supported in Pinescript, to Pinescript. Advanced regression frameworks that can be critical to ticker data, such as Ridge, Lasso, ElasticNET, and Logistic (normalized) regression, colinarity measuring and quantile regression. As well as approaches to linear based feature selection and importance assessments.
I hope you find it helpful!
Library "Regression_Toolkit"
multipleRegression(y, x1, x2, length)
Parameters:
y (float)
x1 (float)
x2 (float)
length (simple int)
ridgeRegression(y, x1, x2, x3, x4, nVars, length, lambda)
Parameters:
y (float)
x1 (float)
x2 (float)
x3 (float)
x4 (float)
nVars (simple int)
length (simple int)
lambda (simple float)
lassoRegression(y, x1, x2, x3, x4, nVars, length, lambda, iterations)
Parameters:
y (float)
x1 (float)
x2 (float)
x3 (float)
x4 (float)
nVars (simple int)
length (simple int)
lambda (simple float)
iterations (simple int)
logisticRegression(y, x1, x2, x3, x4, nVars, length, learningRate, iterations)
Parameters:
y (float)
x1 (float)
x2 (float)
x3 (float)
x4 (float)
nVars (simple int)
length (simple int)
learningRate (simple float)
iterations (simple int)
featureSelection(y, x1, x2, x3, x4, nVars, length)
Parameters:
y (float)
x1 (float)
x2 (float)
x3 (float)
x4 (float)
nVars (simple int)
length (simple int)
regressionStats(y, x1, x2, x3, x4, nVars, length, b0, b1, b2, b3, b4)
Parameters:
y (float)
x1 (float)
x2 (float)
x3 (float)
x4 (float)
nVars (simple int)
length (simple int)
b0 (float)
b1 (float)
b2 (float)
b3 (float)
b4 (float)
elasticNetRegression(y, x1, x2, x3, x4, nVars, length, lambda, alpha, iterations)
Parameters:
y (float)
x1 (float)
x2 (float)
x3 (float)
x4 (float)
nVars (simple int)
length (simple int)
lambda (simple float)
alpha (simple float)
iterations (simple int)
huberRegression(y, x1, x2, x3, x4, nVars, length, huberK, iterations)
Parameters:
y (float)
x1 (float)
x2 (float)
x3 (float)
x4 (float)
nVars (simple int)
length (simple int)
huberK (simple float)
iterations (simple int)
quantileRegression(y, x1, x2, x3, x4, nVars, length, tau, learningRate, iterations)
Parameters:
y (float)
x1 (float)
x2 (float)
x3 (float)
x4 (float)
nVars (simple int)
length (simple int)
tau (simple float)
learningRate (simple float)
iterations (simple int)
Indicators and strategies
Time-of-Day/Session Performance Stats [QuantAlgo]🟢 Overview
The Time-of-Day/Session Performance Stats is a comprehensive time-based analysis tool built for traders who want clear, ranked insight into when markets actually move. It measures average range, volume, bullish bias, and drift across every hour of the day and the four major sessions, then surfaces the strongest and weakest windows so you can focus activity where the data supports it. Whether you trade crypto around the clock or equity and forex sessions on a weekday schedule, the indicator turns raw historical bars into practical rankings, session comparisons, and non-repainting chart overlays.
🟢 What is Time-of-Day and Session Performance?
Markets are not uniform across the 24-hour cycle. Liquidity, volatility, and participation concentrate in specific hours and sessions. Sydney is typically the thinnest of the four major centers, Tokyo drives Asian activity, London often produces the widest ranges of the day, and the London-New York overlap is usually the busiest window. By averaging range, volume, the share of up closes, and net drift for each hour and each session over a configurable lookback, this tool converts those recurring patterns into ranked statistics instead of leaving you to rely on memory or anecdotal observation.
🟢 How It Works
The indicator walks a configurable window of past bars (limited by lookback days and a hard max-bar ceiling) in the timezone you select. Every usable bar is assigned to its hour of day and to any sessions it falls inside. Range can be measured in percent of close or in raw price units. Volume, directional closes, and drift are accumulated in parallel. Hours that do not meet a minimum bar-count threshold are dropped from every ranking so tiny samples cannot distort the boards.
Five ranking boards are produced: Activity (average range), Volume (when the symbol reports it), Bias (percentage of directional bars that closed higher), Drift (mean close-minus-open percentage), and Aggregated (the mean percentile of range, volume, and directional edge). Sessions are ranked solely on average range per bar and can be toggled or given custom windows. Overlaps count toward every session involved rather than being forced into one.
Chart overlays read a trailing window of the same length rather than the final ranking, so background shading and bar coloring never repaint. The Focus Hours panel converts the Aggregated ranking into three practical allocation plans plus the single quietest hour to avoid.
🟢 Key Features
▶ Ranking Boards
Five independent boards list every qualifying hour from strongest to weakest.
1. Activity Ranking: Orders hours by average bar range. Rank 1 is the hour with the most room; the last row is the quietest. This is the simplest and often most useful single board.
2. Volume Ranking: Orders hours by average volume. Read it alongside Activity. High range on low volume signals thin participation. The board is hidden automatically on symbols that report no volume.
3. Bias Ranking: Orders hours by the percentage of directional bars that closed above their open. Flat bars are excluded, so the figure reflects only bars that actually moved. There is no separate bearish column; the bottom of the board is the most bearish reading.
4. Drift Ranking: Orders hours by mean percentage change from open to close. An hour can post a high bull rate yet still show negative drift if its losing bars are larger than its winning ones. Divergences between Bias and Drift are often the most interesting signals.
5. Aggregated Ranking: Combines percentile ranks of range, volume (when present), and directional edge into a single composite score. This is the ranking that feeds both the Focus Hours panel and the Aggregated overlay option.
▶ Session Ranking Panel
The four major sessions are ranked by average range per bar and displayed with their window, bull rate, drift, and bar count. Rank 1 takes the bullish color and the last rank takes the bearish color on the same continuous gradient used by the boards. Because a bar inside an overlap is counted toward every session it belongs to, session bar totals can exceed the overall sample size.
▶ Focus Hours Panel
The Aggregated ranking is translated into four labeled plans: Aggressive (top hour only), Mix (top two with 80/20 weights), Conservative (top three with 50/30/20 weights), and Avoid (the single quietest hour by average range). Each row shows the relevant hours, their session affiliation, bull rate, drift, and score so the reading can be acted on immediately.
▶ Chart Overlay
Background shading and price-bar coloring can be driven independently by Session Ranking, Activity Ranking, Volume Ranking, Bias Ranking, Drift Ranking, Aggregated Ranking, or Focus Hours. All overlays are computed from a trailing window so they never repaint. Transparency controls let you keep the ranking obvious or keep it subtle enough not to compete with price.
▶ Session and Filter Controls
Sydney, Tokyo, London, and New York can each be enabled or disabled and given custom HHMM-HHMM windows in the selected timezone. A weekdays-only filter removes weekend bars for forex, futures, and equities while leaving crypto fully intact. The Bars To Include setting can restrict the entire study to all bars, any enabled session, or one named session.
▶ Built-in Alerts
Ready-made alert conditions fire when price enters the peak activity hour, the quietest hour, the peak volume hour, the most bullish or most bearish hour, or the top Aggregated hour. Separate alerts cover the open and close of each individual session, any session start or end, and the start and end of the London-New York overlap.
▶ Color Presets
Six presets (Classic, Aqua, Cosmic, Cyber, Neon, Custom) apply a continuous gradient from the bullish color at rank 1 to the bearish color at the last rank across every board, panel, and overlay. Custom mode exposes individual bullish and bearish color pickers; text contrast is calculated automatically so any chosen colors remain readable.
▶ Interval Warning
When the chart interval is higher than 1 hour, most of the 24 hour buckets never receive a bar, leaving the rankings incomplete. The indicator displays a clear warning label on the chart that explains the limitation and recommends switching to 5m, 15m, 30m, or 1h, for example. The warning can be turned off once the restriction is understood and a clean chart is preferred.
Universal Signal Backtester [LuxAlgo]The Universal Signal Backtester indicator is a tool designed to simulate and analyze the performance of virtually any trading signal, ranging from moving average crossovers to external indicator triggers. By providing a professional-grade backtesting environment directly on the chart, it allows users to optimize entry logic, exit strategies, and cost management in real-time.
🔶 USAGE
The script operates by selecting a Source Mode to define how trades are initiated. Users can choose between built-in MA crosses, external source crossovers, or discrete signal triggers from other indicators. Once a signal is detected, the script simulates a trade using up to three Take Profit (TP) and three Stop Loss (SL) levels.
🔹 Trade Visualization
* Sign Posts: Upon entry, the script plots a "Sign Post" label below or above the bar. This label dynamically suggests which TP level is currently the most effective based on the selected metric (e.g., Hit Rate or Expected Profit).
* Active Exit Lines: Horizontal dashed lines extend from the entry point to visualize the selected TP and SL targets. These lines update in real-time and mark hits with a checkmark (✓) or an "X" (✗).
* MA Ribbon: When using predefined crosses, a gradient ribbon is displayed to visualize the trend strength and crossover points.
🔹 Signal Modes
* Predefined Crosses: Quickly test standard strategies like the 9/21 EMA, 12/26 EMA, or the Golden/Death Cross (50/200 SMA).
* External Sources (Crossovers): Select two external plots (e.g., RSI and a level, or two custom MAs) to test their crossover performance.
* External Signals (Triggers): Connect the script to discrete signals such as Plotshapes or Boolean conditions. This is ideal for testing "Buy" and "Sell" signals from specialized indicators.
🔶 DETAILS
🔹 Advanced Analytics Dashboard
The indicator features a robust dashboard providing institutional-level metrics:
* Core Metrics: Total trades, Win Rate, Profit Factor, Sharpe Ratio, and Recovery Factor.
* Equity Curve: A sparkline representation of the account growth over time.
* Hourly Histogram: A performance breakdown by the hour of the day, helping identify the most profitable trading sessions.
* Heatmaps: A "Days of the Week" or "Monthly" heatmap that colors cells based on profitability, allowing for the identification of seasonal or periodic performance patterns.
🔹 Cost Simulation
To ensure realistic results, the script includes a cost engine. Users can select from profiles like Forex, Crypto, or Stocks to automatically apply industry-standard spreads and commissions, or input manual values to match a specific broker's fee structure.
🔶 SETTINGS
🔹 Source Settings
Source Mode: Determines the logic for trade entries (Predefined, External Crossover, or External Trigger). Signal Logic: Defines how external triggers are interpreted (e.g., Value Changes, Crosses 0, or Not NA). Trade Direction: Filters signals to allow only Longs, only Shorts, or Both.
🔹 Filters
Use ATR Choppiness Filter: When enabled, the script ignores signals that occur during low-volatility "choppy" periods.
🔹 Target Settings
Distance Type: Sets the measurement unit for TP/SL levels (ATR, Ticks, or Points). Take Profit (1-3): Toggles and sets the distance for up to three partial take-profit levels. Stop Loss (1-3): Toggles and sets the distance for up to three stop-loss levels.
🔹 Costs
Simulate Spread & Commission: Enables the cost engine for more realistic PnL calculations. Cost Profile: Presets for Forex, Crypto, and Stocks, or "Manual" for custom inputs.
🔹 Dashboard & Visuals
Heatmap Period: Switches the dashboard heatmap between "Days of Week" and "Months". Suggested TP Metric: Chooses the criteria the "Sign Post" uses to suggest the best TP level. Gradient Candle Coloring: Colors candles based on the distance between the fast and slow sources.
MACD with HTF Panels [theUltimator5]This indicator uses the standard MACD signal and displays it on multiple timeframes off to the side of the chart as separate panels. It allows you to view up to 5 (current + 4) separate timeframes at once without changing the chart timeframe.
The core of this indicator is a normal MACD. Line, signal, and histogram, fully adjustable lengths, four-way histogram coloring based on sign and slope so you can read momentum shifts at a glance.
Alongside it, the indicator draws up to four independent higher-timeframe panels directly in the pane. Each panel is a self-contained mini-chart: recent candles (if enabled) plus that timeframe's own MACD line, signal, and histogram.
The default settings show (4) HTF panels, that are automatically set based on what the current chart timeframe is. The HTF panels can be manually adjusted as desired.
The number of HTF panels can be adjusted between 0 and 4. 0 panels is set by disabling the "Show HTF panels" option in the settings. Disabling the HTF panels simply makes this indicator appear as a standard MACD, which isn't unique and can probably be found in 1000 other indicators elsewhere.
The uniqueness comes from the HTF panels on the side.
The panels settings will match for all timeframes.
When there are between 1 and 3 HTF panels enabled, they will appear vertically stacked on each other.
The following image shows (3) panels stacked.
When 4 panels (default) is selected, the stack is 2x2.
The chart price candles can be enabled in the settings to show the candles overlaid on the MACD signals for a complete picture. These are disabled by default to reduce visual clutter.
Here is an image showing (4) panels with chart candles enabled within the panels.
The scaling and position of the panels can be adjusted in the settings.
The vertical scaling is based on ATR, so the height of the candles is always proportional to the chart itself.
The horizontal position is calculated in bars, so it can be shifted left or right relative to the current bar.
Finally, there is a table (default position = bottom right)
The table shows the values of the MACD, Signal, and Histogram for all (4) HTF panels. The table position and size can be modified, but it will always show all (4) HTF panel values. If you have only one HTF panel enabled, it will still show you all values. The Histogram column changes color based on the color of the HTF histogram for that timeframe to give a better visual representation of the signal.
This indicator was made to showcase the method for converting custom signals into HTF side panels for custom and complex indicators. The logic used in this indicator can be used on any plotting indicator to display the signals off to the side.
ChessPlay real, full games of Chess against the computer , live on your chart. The first playable chess on PulseWire.
🟩 HIGHLIGHTS
⭐ Genuine independent gameplay - infinite different games possible.
⭐ The computer ranked over 1200 at Blitz across 100+ rated games on a popular online chess website.
⭐ Optional trash talk from the computer.
⭐ Play as White or Black, with the board flipped so your pieces sit at the bottom.
⭐ The computer thinks and responds fast.
⭐ Only legal moves are allowed.
⭐ The computer knows all the rules of chess including en passant, castling, check, checkmate, and the threefold repetition draw.
⭐ The to and from squares of the most recent move from both sides are highlighted on the board.
⭐ Captured pieces are displayed; just-captured pieces are highlighted.
⭐ The colour scheme adjusts to light or dark chart backgrounds.
⭐ The computer sometimes resigns if it's about to lose, and sometimes lets you complete the win 🎉
🟩 SET UP THE FIRST GAME
Add the script to a chart with some history (it warns you if there's not enough); any timeframe, doesn't matter if the market is open or not.
The script opens on a separate pane below the main chart, appearing cut-off.
1. Don't panic.
2. Double-click the chart background to maximise the Chess pane and hide your chart (double-click again to get your chart back).
3. As an alternative, you can click the three dots next to the Chess script name and choose Move to > Existing pane above and then Hide the chart symbol.
4. The default settings should be good to get you started playing, although you might need to adjust the square height and width so that the chess board looks nicely square on your particular chart.
🟩 PLAY THE GAME
Let's play.
1. Open the indicator Settings dialog and move it to the side so you can see the board.
2. Type your move into the Moves text field. Use coordinate notation: `e2e4`, `g1f3`, `e7e8q`, etc.
3. Don't click OK , 'cos then you'll have to re-open the Settings. Instead, hit Enter . This makes the script pick up the changed text input.
4. The computer thinks a little (on my system, almost instantly) and then announces its move. It will say something like `Add my move d7d5 to the Moves input field`. The table cell goes orange.
5. Type that move into the Moves input field and hit Enter . The cell's orange colour disappears, and it tells you that it's your move. If you have trash talk on, it might comment about your move or its move.
If you enter an illegal move, the computer will tell you. It will try to help you if it recognises what you might have meant.
If you enter a move wrong, you can just delete it and press Enter and the game resumes from that point.
To save a game or show it to someone else with the same Chess script loaded, just copy the text out of the Moves input field (you'll need Replies: Deterministic set to ensure it makes the same next move).
Have fun! The computer will joke around with you even as it beats you (or you beat it).
🟩 RELOAD THE CHART TO PLAY THE NEXT GAME
To ensure that the computer can (if it wants) play different moves against the same position next game, leave Replies set on Random, and save and reload the board in between games.
Note that moves that are already entered are not changed. Only a pending move - one that the computer announced but you haven't yet typed in - can change if you reload the chart or change input values, and only if Replies: Random is selected in the settings.
🟩 ALL THE SETTINGS
Here are all the settings and what they do.
Moves : Holds your moves and the computer's moves. You can separate moves with a comma or a space or a new line. Only a new line, or some other change to the settings, triggers a script refresh and another move.
Play as : Choose who you want to play as. The board flips so that your pieces are always closest to you.
Thinking : Choose Deep so that the computer uses all of its tiny brain against you. Or Quick if you feel that it's responding too slowly or you run into timeout errors.
Replies : Choose Random so that the computer can play different moves against the same position next game (requires a chart reload to clear cache). It often still plays its favourite move but if it has two favourites, it can also choose between them. Choose Deterministic so that the computer always plays the same move given the same position (move sequence), even on a different chart, tab, or after a reload. This is useful for saving games or playing through saved games. For casual play, choose Random . Note : If you change any inputs to a new combination, the computer might choose a different reply if Random is selected.
Trash Talk : Enable this setting so the computer can misquote pop culture references at you.
Board : Move the board to the left, right, or centre.
Piece size : Make the pieces more bigger or smallerer.
Square width, height : Adjust the percentage sizes so that the squares look, well, square. The right ratio depends on your monitor.
Calculated Bars : This setting is precautionary to prevent any slowdown on long charts. Leave this at the default.
🟩 CREDITS
Thanks to @The_Peaceful_Lizard for discussions all that time ago about whether gameplay is possible in Pine.
Thanks to my beta testers for valuable usability feedback.
🟩 HOW TO CODE GAMEPLAY IN PINE
Turn-based gameplay as we know it is almost impossible. To play a game you need to make a move. So you need to tell the script something. All the possible ways to interact with a script are:
- Changing an input value (including interactive `input.time()` and `input.price()` lines).
- A value output from another script changing, if the consuming script reads it using `input.source()`.
- Scrolling or zooming the chart, if the script uses `chart.left_visible_bar_time()` or `chart.right_visible_bar_time()`.
- Changing the chart symbol or timeframe.
The latter two are not informative enough to build a game move from. And in any case, any change from this list reloads the script.
What happens when the script reloads? It forgets everything . This is actually a good thing, and by design, because a chart indicator needs to read from bar zero again if any of its inputs change, so that it can do all its calculations again and make sure they are accurate with the new settings. However, it means that there is nowhere to keep the game state :
- All variables reset, even `var` and `varip` ones.
- All drawings, plots, shapes, etc disappear.
- Pine cannot write to any external data source.
- Logs start from zero.
The only thing that persists is the most recent input values.
This means that the entire game state must be deterministically derivable from the values of inputs. Practically speaking, you must enter both your move and the computer's reply into inputs. That's the trick: the board is not stored between edits, it is re-derived from the move record (the computer checks ALL moves again, every move, that they are legal).
This pattern is actually already demonstrated in the only example of true gameplay in Pine that I was able to find: "Tic Tac Toe (For Fun)" by the Wizard @LonesomeTheBlue
🟩 HOW TO MAKE CHESS POSSIBLE IN PINE
The problem with chess is that there are so many possible moves that trying to foresee the responses to your move and the responses to that move and so on quickly becomes computationally impossible. Especially in a lightweight scripting language like Pine, which has limits on how long a script can take to run a loop or perform all its calculations.
The main thing you need to do is narrow it all down . So in building this thing from scratch, I started with some heuristics. What are some patterns when you play chess? If you're in check, you have to get out. If you're attacked, you defend. If you still have minor pieces on the back rank, you should probly get them out. And so on.
I quickly realised that not only did these rules hugely narrow down the number of things you need to calculate, but they stack in order of importance . The number one rule, for example, is: if you can win the game this move, you should. Nothing else can go before that. Developing your pieces, by contrast, comes somewhere at the back of the queue. And the others fall in line in between.
Another thing that comes fairly cheap is knowing the openings . I was always too lazy to memorise chess openings, but that's not a problem for a computer. I also figured that nailing the opening would put my engine in a good place for the midgame, and maybe cover up some weaknesses. Many of the rules are pitched slightly aggressive for the same reason 😆
🟩 MY LADDER
When you put a bunch of rules in priority order, you get a ladder .
SPOILER: If you want to enjoy a fresh game against the script, stop reading and play. Going further will give you insight into how it thinks (or avoids thinking) and allow you to beat it more easily.
Here's my chess ladder:
1. Mate in one .
2. Opening book .
3. Avoid checkmate .
4. Defence .
5. Win material .
6. Safe check .
7. Exchange when ahead .
8. Endgame .
9. Develop .
10. Any other safe move .
When you follow this ladder in order, it turns out, it looks quite a lot like you're playing chess. After I'd finished the script I found out that some very early chess engines did something similar (they likely had far weaker performance than mine), but modern ones function mainly by crunching predictions and even the most handicapped Stockfish engine is still stronger than this script.
On top of the ladder, you do need to layer in a little bit of actual looking ahead, or the engine makes terrible blunders. And you need to constrain that lookahead in turn with its own heuristics, so that you only map out a few moves and not an exponential number.
And there is a lot, lot more after that, like how we value pieces (we will deliberately lose material in an exchange when we're up big, because it hurts our opponent more), how we avoid forks, how we avoid repeating the same moves, and so on.
Still this is all smoke and mirrors - the engine doesn't really understand what makes a good position, or a good attack, or an elegant defence. It just does stuff.
🟩 HOW THIS SCRIPT WORKS END TO END
Here's what happens when you type a move and hit Enter:
Parse (bar zero). The input move text becomes board coordinates (via the ChessCore library). The tidied-up record also produces the random seed (via ChessAI), and the opening book (ChessBook) and the trash-talk banks (ChessTalk) get built.
Replay (one bar per move). For each move in the record, in order: generate every legal move in the current position, check that the next typed move is one of them, and if so apply it to the board. Along the way it collects captured pieces, watches for Game Over, and takes a fingerprint of each position so it can spot a threefold repetition. All ChessCore.
Prepare (one bar). If it's now the computer's turn: generate ALL its legal moves (ChessCore), note how well defended each move's landing square is, and flag moves that weaken the king's cover or just undo the previous move (ChessAI).
Foresee (up to three bars per candidate move). Score every potential move by how its story ends, in material: the opponent's best captures played all the way down the exchange, his checks, his quiet threats. The ChessAI library plays the stories out on ChessCore scratch boards.
The ladder (one rule per bar). Try the ten rules top down. The first that fires chooses the move. The scores from the foresee veto the doomed candidates in every rule, and break ties in some. ChessAI does all this, and rule 2 reads the ChessBook openings.
Done. The live bar draws the board and the status row announces the computer's move for you to type in. The live bar computes nothing - it only reads what the historical bars decided. The main Chess script draws, and any one-liners came from ChessTalk's banks.
Then you add its move to yours, hit Enter, and the entire thing runs again from bar zero: approves every past move, rebuilds the whole board, and chooses the next reply.
🟩 ARCHITECTURAL LESSONS
There are some things we did here that apply also to non-chess Pine scripts.
✨ "Library-first" design
Every large chunk was designed from the beginning to be its own library:
- Chess is the user-facing script that imports all the libraries.
- ChessAI decides the next move.
- ChessBook holds the openings.
- ChessCore does rules, the board, and what's legal.
- ChessTalk stores the trash talk.
Some libraries only hold data, and this split of calculations and data allows us to, for example, add more talk lines without republishing other libraries.
The main script and libraries together total ~5,000 lines, far too much to be manageable for a single script. Using libraries keeps everything organised and makes such a complex script possible.
Library-first design keeps you in control, as opposed to being forced to refactor functions into libraries when a script gets too big.
✨ Split work across bars
We do the work across historical bars, one small step per bar, so the live bar only has to draw the finished board. This prevents any one bar exceeding any timeouts. The ladder architecture makes this quite natural. For robustness, each stage declares its own completion, rather than using a hardcoded number of bars. We predict how many bars we'll need and warn if there's not enough. This pattern can help scripts with heavy calculations stay within budget.
✨ Draw once
We do all the calculations during historical bars and draw the board only on the last bar. In general, separating calculations from drawing is a good idea.
✨ Seeded randomness
If you want a random result (technically, pseudorandom) in Pine you can use `math.random()`. This returns a different number each time. We use random numbers to choose between equally promising options for the computer's moves.
However, sometimes you want a reproducible random result. You want the same random number each time the script runs. Fortunately, `math.random()` can take a `seed` parameter, and if this seed is the same, then the sequence of random numbers is the same.
For chess, the computer's choice of move must remain stable and not change to something else on the next tick of a realtime bar (seeing this happen in testing was quite a surprise). So we derive a seed from the move record . This means that for any given game, the computer's move is both random and repeatable. This helps a lot with testing.
For real games though, we want the user to be able to play the same moves next game and get a different reply from the computer (potentially; remember the computer can't remember what it did in any previous game). So for this reason, if Replies is set to Random, we also mix the script's loading time into the random seed (and each new move loads the script again). Reloading the chart now gives a new stable random sequence even for the same moves.
Understanding seeded randomness can be necessary for some scripts to use variety properly.
✨ Objects and library hierarchy
Libraries can import each other, but not in a circular way. Our lowest-level library declares object types, for example, for moves, but doesn't put data in them; the higher libraries do that.
When a more foundational library needs data that a higher-level library creates, a good pattern is for the lower library to create an object with fields that the higher library fills in. This avoids having to maintain parallel arrays so they don't get out of synch.
Directional Volume Shapes (Zeiierman)█ Overview
Directional Volume Shapes (Zeiierman) is a regime-classification oscillator that reframes volume analysis around a different question: not simply “how much volume traded,” but “what statistical shape has directional pressure been forming, and which way is it leaning?”
Instead of plotting raw buy and sell volume bar by bar, the indicator scores each candle for directional pressure using a triangular intrabar distribution model. It collects those scores in a rolling window, classifies the pattern into one of seven distribution shapes, and displays a smooth synthetic template of the detected shape.
The result is less like a traditional volume indicator and more like a distribution-regime display, showing the type of pressure environment currently developing.
⚪ Why Is This One Unique?
Most volume tools show exactly what happened: green bar up, red bar down, and taller bar equals more volume. This indicator uses a two-stage process: classify, then synthesize.
It combines:
• A triangular CDF candle scorer that estimates directional pressure from OHLC data
• A rolling shape classifier using skewness, Gaussian-smoothed peak detection, and time correlation
• Seven possible classifications: Bell, Right-skewed, Left-skewed, J-shaped, Reverse-J, Bimodal, and Multimodal
• A template generator that displays an idealized mathematical version of the active shape
• A separate EMA-based polarity engine that controls bullish or bearish direction
█ How It Works
⚪ 1. Scores Each Candle’s Directional Pressure
Instead of using a simple “close above open equals bullish” rule, the indicator models the candle’s high-low range as a triangular probability distribution centered at the close.
The scr() function evaluates the candle’s full OHLC structure and returns a value between 0 and 1. That result is then converted into a signed pressure score between -1 and +1.
dm = scr(open, high, low, close)
ps = 2.0 * dm - 1.0
Values near +1 represent stronger bullish pressure, while values near -1 represent stronger bearish pressure. Values near zero indicate a more balanced candle.
⚪ 2. Optionally Weights Pressure by Volume
When Volume Weighting is enabled, the pressure score is multiplied by raw volume.
src = vw ? volume * ps : ps
This gives high-volume bars more influence over the rolling shape-classification window. When disabled, the classifier uses directional pressure alone.
Volume still controls the height of the plotted columns regardless of this setting.
⚪ 3. Classifies Pressure Shape, Not Direction
The indicator stores recent pressure values in a rolling window. Before classification, it converts each value into its absolute magnitude.
for i = 0 to buf.size() - 1
mag.set(i, math.abs(buf.get(i)))
Using math.abs() removes bullish and bearish direction from the classification stage. The classifier analyzes how pressure strength has been distributed, not which direction it points.
It measures:
• Skewness in the raw pressure magnitudes
• Local peaks in a Gaussian-smoothed version of the data
• Whether pressure strength is generally increasing or decreasing through time
The final shape is selected using a fixed priority order:
if peaks >= 2
out := peaks == 2 ? "Bimodal" : "Multimodal"
else if corr > 0.5
out := "J-shaped"
else if corr < -0.5
out := "Reverse-J"
else if skew > 0.1
out := "Right-skewed"
else if skew < -0.1
out := "Left-skewed"
else
out := "Bell"
Multiple peaks are checked first, followed by rising or falling behavior, then skewness. Bell is used when no other condition is detected.
⚪ 4. Requires Persistence Before Changing Shapes
The active shape changes only after five consecutive bars produce a classification different from the shape currently displayed.
if ns != sh
sc += 1
else
sc := 0
if sc >= 5
sh := ns
ph := 0.0
sc := 0
The five classifications do not need to match each other. They only need to differ from the current active shape.
When the fifth differing classification arrives, the indicator switches to that bar’s shape and restarts the template cycle.
⚪ 5. Tracks Polarity Separately
Bullish or bearish polarity is calculated independently from the shape classification.
A short EMA is applied to the original signed pressure score:
pr = ta.ema(ps, pl)
string np = pr >= 0 ? "Bull" : "Bear"
When the EMA is above or equal to zero, polarity is Bull. When it is below zero, polarity is Bear.
Because polarity can change as soon as the EMA crosses zero, it usually reacts faster than the shape classifier.
⚪ 6. Displays a Synthetic Shape Template
Once a shape is selected, the indicator does not plot the original pressure values.
Instead, it generates an idealized mathematical template for the active shape. For example, Bell uses a Gaussian curve, J-shaped uses a squared rising curve, and Bimodal combines two separate Gaussian peaks.
The generated template is then scaled by recent average volume and signed according to polarity.
p = pol == "Bull" ? ph : 1.0 - ph
tv = tpl(sh, p)
sgn = pol == "Bull" ? 1.0 : -1.0
amp = ta.sma(volume, 3) * 1.8
y = amp * tv * sgn
The template advances by a fixed amount on each bar. Template Cycle Length controls how many bars are used to complete one full cycle.
█ Assumptions We Are Explicitly Making
The indicator’s usefulness depends on whether its modeling assumptions are suitable for the instrument and timeframe being analyzed.
These are not facts about market behavior. They are simplifying assumptions used because Pine Script does not provide true intrabar tick or order-flow data.
⚪ Intrabar Activity Is Approximated With a Triangular Distribution
The model approximates intrabar activity using a triangular distribution centered at the close. It does not know where price actually spent the most time within the candle.
Using another reference point, such as VWAP, the midpoint, or the open, could produce a different pressure score.
⚪ Shape and Direction Are Treated Separately
The shape classifier analyzes the magnitude of pressure but removes its bullish or bearish direction. Two windows with similar pressure-strength patterns but opposite directional bias can therefore receive the same shape classification.
The shape describes how pressure has been distributed, while the separate polarity calculation determines whether it is leaning Bull or Bear.
⚪ Seven Shapes Are Used to Describe Pressure Behavior
Every window is placed into one of seven fixed categories using predefined thresholds:
• Skewness thresholds of ±0.1
• Correlation thresholds of ±0.5
• Peak prominence above 10% of the smoothed envelope’s maximum
The classifier follows a fixed priority order rather than selecting the mathematically closest-fitting shape.
There is also no statistical significance test behind these thresholds, so borderline classifications may change because of noise.
⚪ The Displayed Curve Represents the Classification, Not the Raw Data
After classification, the indicator displays an idealized template rather than the original pressure values. Two different pressure windows classified as Bell will use the same normalized Bell template.
The final column height and direction can still differ because the template is scaled by recent volume and signed by polarity.
█ How to Use
⚪ Directional Volume Reading
Use the indicator as you would a traditional volume oscillator.
• Readings above zero indicate bullish volume strength.
• Readings below zero indicate bearish volume strength.
⚪ Divergences
Use the columns to identify divergences in volume strength.
• Bullish divergence: Price makes a lower low while the indicator forms a higher low.
• Bearish divergence: Price makes a higher high while the indicator forms a lower high.
⚪ Interpreting the Shape Labels
• Bell: Pressure intensity is relatively symmetric and contains one main area of activity.
• Right-skewed / Left-skewed: Pressure intensity is uneven and has a longer tail on one side of the distribution.
• J-shaped: Pressure intensity has generally increased toward the most recent bars.
• Reverse-J: Pressure intensity was stronger earlier in the window and has weakened toward the present.
• Bimodal / Multimodal: The smoothed pressure path contains two or more separate periods of stronger activity within the detection window.
⚪ Choosing the Shape Speed
Template Cycle Length controls how quickly the displayed shape moves through its synthetic cycle. It changes the visual speed of the columns, not the shape-detection window or Bull/Bear polarity.
• 3 bars, Fast: Creates tight, fast-moving shapes. This is the most responsive and active-looking setting.
• 4 bars, Balanced: Gives each shape slightly more time to develop while remaining responsive.
• 5 to 7 bars, Slow: Stretches the shape across more bars, creating smoother and slower visual cycles.
A value of 3 is useful when you prefer compact, fast-moving shapes. Increase the value when you want each shape to develop more gradually and remain visible for longer.
█ Settings
Use Volume Weighting: Controls whether volume multiplies directional pressure before shape classification. Volume still controls the plotted column height when this setting is disabled.
Detection Window: Sets the number of recent bars used to classify the current shape. Higher values produce slower and more stable classifications. Lower values react faster and may change shape more often.
Polarity Smoothing: Sets the EMA length used to determine Bull or Bear polarity. Higher values create steadier polarity. Lower values react faster.
Template Cycle Length: Sets the number of bars used to complete one synthetic shape template. Lower values create faster and tighter cycles. Higher values stretch the template over more bars.
Show Moving Average: Shows or hides a moving average of the final plotted output.
Type: Selects the moving-average method: SMA, EMA, RMA, or WMA.
Length: Sets the moving-average period.
Maximum Transparency: Sets the maximum transparency applied near the lower points of each template. A value of 0 disables the transparency fade.
-----------------
Disclaimer
The content provided in my scripts, indicators, ideas, algorithms, and systems is for educational and informational purposes only. It does not constitute financial advice, investment recommendations, or a solicitation to buy or sell any financial instruments. I will not accept liability for any loss or damage, including without limitation any loss of profit, which may arise directly or indirectly from the use of or reliance on such information.
All investments involve risk, and the past performance of a security, industry, sector, market, financial product, trading strategy, backtest, or individual's trading does not guarantee future results or returns. Investors are fully responsible for any investment decisions they make. Such decisions should be based solely on an evaluation of their financial circumstances, investment objectives, risk tolerance, and liquidity needs.
IQ Trend Beams [TradingIQ]🔹 OVERVIEW
IQ Trend Beams is a trend assistant that draws your trendlines the way a disciplined chartist would - and then holds them accountable. It maintains two channels, support and resistance , each always showing one working line. A line is born forming : it moves and re-shapes freely, polished every bar by a perceptual score toward the line a skilled trader would actually draw. When its geometry settles and it has earned enough tangency credit, it locks - and from that moment the ink is frozen forever; it never moves again. Locked ink extends until break evidence fires, then it is broken : restyled but never relocated, holding the screen as history until its successor locks.
Riding each live beam is its own forecast ; a calibration band, a reach profile, and ghost levels, all built from the volume that has actually traded around that line.
This is an honest visualization and modeling tool , not a signal service. It draws structure clearly and states its own confidence out loud; it is not a validated edge or a promise of profit. Read the limitations section - it is not window dressing.
🔹 THE TWO CHANNELS - AN AUDITED PROMISE
Most trendline tools quietly redraw the past so the line always looks right in hindsight. Trend Beams refuses to. A line lives through three visible states:
• Forming (dotted) - the assistant sketching. It is free to move and re-fit while it hunts for the right geometry. This is the only state in which a support/resistance line moves, and it is dotted precisely so you can tell a guess from a commitment.
• Locked (solid) - the geometry has stilled and earned its tangency credit, so the line is frozen . It will never move again. A locked beam is a promise the tool has to keep in public.
• Broken (restyled) - break evidence fired. The ink is re-styled to show it failed, but it is never relocated ; it holds its original slope as an honest record and, if you keep history on, dims into the background once its successor locks.
Because a locked line cannot move, what you saw at lock time is what you keep. This is the core design commitment of the tool.
Two rails, either direction by design. Support is the lower rail, fit to the swing lows on the underside of price; resistance is the upper rail, fit to the swing highs above it. Neither is locked to a single slope: in a falling market the support rail angles down with the lows (the floor of the down-channel), and in a rally the resistance rail angles up with the highs (the ceiling of the up-channel). That is deliberate. A tool that forces support to only ever point up would go blind to the lower boundary of a downtrend - and miss exactly the moves that matter. Trend Beams instead always draws both boundaries of the channel price is actually in , so a strong move is framed on both sides rather than half-missed. If you prefer to read it the classical way, follow the rail that agrees with the trend and treat the other as the opposite wall of the same channel.
🔸 HOW A LINE EARNS ITS LOCK
While forming, each line is scored every bar by a perceptual fit , a running measure of how well its geometry matches what a careful trader would draw against the recent swing structure, blended with a one-pole toward its fitted slope so it settles rather than twitches. A lock is granted only when the geometry has gone still for long enough, the line has accumulated real tangency credit (genuine touches, not a single graze), and it spans a minimum bar count - and it is refused outright if it would invert the channel. The Mode dial sets how much evidence this takes.
🔹 THE AUDIT BADGE
Locked ink can carry a small measurement badge that reports, in plain terms, how the line is actually holding up:
• Wick-through - recent piercing of the line, exponentially weighted, measured against the tool's 10% design target . A well-behaved line lets price kiss it, not knife through it.
• Survival probability - the current modeled odds that the line is still valid.
• Maturity - how far through its estimated total run the move is, so a young trend reads differently from an exhausted one.
The badge is the tool grading its own work on the chart, not a trade instruction.
🔸 THE FORECAST - EACH BEAM READS ITS OWN VOLUME
Every live beam carries its own forecast, built entirely from the volume that has traded around that line. Trend Beams bins the intrabar volume by its distance from the beam, smooths it into a continuous density (a kernel-density estimate), and renders three things that ride the line:
• Calibration band - translucent ribbons hugging the beam, one per density bin, showing where the trend has held its volume. Strength is encoded as colour vibrancy at a constant perceptual lightness (the Oklab principle - a dense core reads vivid, the thin tails fade), so nothing is made brighter or darker than its weight warrants.
• Reach profile - a smooth filled contour fanning into the future margin, where each level's forward extent is its density times the trend's estimated remaining length . It answers, at a glance: if this trend keeps going, how far - and around which prices - does its own volume say it reaches?
• Ghost levels - dashed lines at the distribution's densest peaks, riding parallel to the beam, marking the prices this trend keeps returning to.
The forecast attaches only to a beam's currently-visible live element - its forming sketch, or its locked ink - and keeps no history . It is a read of the present trend, refreshed at the live edge, not a replay of the past.
🔸 THE ENGINE DIALS
• Mode - the tempo. Fast locks, breaks and re-forms sooner (short swings); Slow demands more evidence and holds through more noise (long moves); Medium is the balanced reference.
• Precision - how much data the engine reads: the perceptual fit window and the intrabar sample rate. Higher tiers resolve finer structure at more load. Sampling is timeframe-aware and never drops below one minute.
🔹 LAYERS, COLOUR & LEGIBILITY
Every layer is a toggle - forming lines, broken history, audit badges, and the forecast - so you can run it as a bare two-line channel or a fully dressed read. Colours come from three clean anchors: Support , Resistance , and Chrome (badges and neutral furniture). The whole translucent forecast - band, profile, and ghost levels - is coloured in the Oklab perceptual space, so strength shows up as vibrancy at a constant lightness rather than as glare, and a single Contrast dial scales the entire forecast from a whisper to bold.
🔸 HOW TO READ IT
• Treat a forming (dotted) line as a hypothesis and a locked (solid) line as a committed level - the tool is telling you which is which on purpose.
• Watch the audit badge : rising wick-through and falling survival probability say a locked line is wearing out.
• Read a broken line as a failed level that still marks where the structure gave way.
• Use each beam's band to see where its trend has held its volume, its reach profile for how far the trend's own volume says it can run, and its ghost levels for the prices it keeps returning to.
🔹 INPUTS
• Trend Engine - Mode (tempo) and Precision (data depth).
• Layers - show forming lines, broken history, audit badges, and the forecast.
• Colors - Support, Resistance, and Chrome anchors, plus a Contrast control for the translucent forecast.
• Channels - enable the support and/or resistance side independently.
🔸 LIMITATIONS AND HONEST NOTES
• This is a drawing and modeling assistant , not a validated strategy. It makes no performance claim and no edge claim . Nothing here is financial, investment or trading advice.
• Locked and broken lines do not repaint - once a line locks, its geometry is frozen. Forming lines move by design (they are the live sketch, and are dotted to say so), and each beam's forecast (band, profile, ghost levels) refreshes at the live edge as new volume arrives and attaches only to the current live element. These are live reads, on purpose; none of them rewrites confirmed history.
• Survival probability, maturity, remaining length and the reach profile are model estimates from the trend's own statistics - projections, not guarantees, and not forecasts of price.
• Intrabar sampling is subject to your plan's intrabar data limits ; higher Precision tiers read more intrabar data.
• Drawing budgets are finite. The tool caps its lines, labels and polylines internally, but very long histories with everything enabled push against PulseWire's per-script drawing limits - trim the layers you don't need.
TASC 2026.08 An Ag Selling Model█ OVERVIEW
This strategy implements the "Ag Selling Model" as presented by Perry J. Kaufman in the August 2026 edition of the TASC Traders' Tips "Identifying The Best Price Levels For Selling Commodity Futures". The article describes a long-hold selling strategy for agricultural commodity futures based around the seasonal harvest timing of crops, mainly US grains traded on the CBOT and KCBOT, both which have been acquired by CME.
█ CONCEPTS
This model was originally presented in 1978 by the author to a group of commodity producers as an attempt to identify the best price levels to sell their products.
The core idea is that if there is only one crop per year, crop prices will be lowest around harvest and highest around peak growing season. Based on this timing, the strategy spaces out its sell orders up to three times throughout the year, and covers its position at harvest.
The goal of this strategy is simply to beat the average price. Since selling at harvest should typically provide a lower-than-average price exit, success for this strategy means having the average of its entries above the average price.
The level to sell at can be determined by finding a moving average that reflects seasonal changes. Once found, we measure volatility using Average True Range (ATR).
With these two figures, the volatility is added to the average based on a multiplication factor.
This creates a reasonable extreme at which to position short entries.
█ THE RULES
Sell short at the selling level.
Delay these sells to ensure two sells are not in the same rally.
Avoid selling immediately after harvest, as a long period of low prices typically follows.
Exit positions (cover shorts) at harvest.
▌Properties
IMPORTANT NOTE: The strategy parameters have been adjusted specifically for Corn Futures (ZC1!). This ticker operates in Cents (USX) rather than Dollars (USD); all the strategy values have been translated to account for this. To apply this strategy to other markets it is important to properly adjust the strategy parameters to simulate realistic conditions.
Initial Capital : 15,000,000¢ == $150,000; see note above.
Position Sizing : This strategy sells in one-contract increments up to three times per year.
Commissions : Commission value is set to 300¢ ($3) per order, which is a generous estimate.
Slippage : Slippage is set to one tick to simulate reasonable execution conditions.
█ INPUTS
Source : Source for calculations.
MA length : Moving Average length (Simple Moving Average). A 20 to 60 day range is recommended; with 40 as a starting point.
ATR length : Average True Range length.
ATR factor : Factor by which to multiply ATR when calculating selling level. 2.5 to 3.5 is generally recommended but higher has been seen for more volatile grains.
Month of Harvest : Set the month of harvest for the crop being traded, which changes depending on the seasonality of the commodity.
Delay in months after harvest : Set this to the typical downtime after harvest where prices are typically lowest. This can vary per instrument but 2 months is the suggested point for tuning.
Days between trades : Days to wait between sales.
Machine Learning Neural Network EngineMachine Learning Neural Network Engine turns complex Daily market behavior into three clear states: LONG, WATCH and CASH.
Instead of relying on one fixed trend signal, the indicator combines an adaptive neural network, continuous model validation and an independent crisis detector. The result is a simple visual interface backed by a fully causal machine-learning process.
HOW IT WORKS
At its core is a compact 6-5-1 neural network trained directly on the chart.
It analyzes six normalized features:
Short- and medium-term trend structure
RSI momentum
Deviation from linear regression
Directional price efficiency
Relative volatility
Candle pressure adjusted by relative volume
The network learns sequentially from completed market outcomes. On each confirmed Daily bar, it can only train on information from an earlier bar whose result has become known. Current predictions never use future data.
Training uses nonlinear neurons, RMS-scaled gradient updates, error clipping and regularization. This is an adaptive online model, not a set of fixed coefficients labelled as machine learning.
SELF-AUDITING MACHINE LEARNING
The neural network is continuously compared with an independent structural trend model.
When the network’s matured predictions provide useful additional information, its influence increases. When its recent error becomes worse than the structural baseline, its influence is automatically reduced.
This live validation mechanism prevents the indicator from trusting its machine-learning component unconditionally.
CRISIS DETECTION
A separate stress engine monitors:
Rapid 10-day declines
Drawdown from the 63-day high
Abnormal ATR expansion
Long-term price structure
This layer can trigger a defensive state independently of the neural model, helping the indicator respond to sudden market deterioration.
HOW TO READ IT
LONG — Green
The model, trend structure and confirmation rules support a constructive market environment.
WATCH — Amber
The market remains structurally LONG, but risk or exit evidence is increasing.
CASH — Red
The environment is defensive because of persistent weakness or confirmed crisis stress. The indicator never takes short positions.
The colored neural axis and surrounding halo display the active state without covering the chart with labels. Transition pulses identify confirmed changes, while the dashboard shows bull probability, neural risk and the current machine-learning audit.
WHAT MAKES IT DIFFERENT
The script integrates four distinct functions:
1. Online neural-network learning
2. Live error-based model validation
3. Independent downside-stress detection
4. A confirmed state machine designed to limit excessive switching
These components are not combined as a simple indicator vote. Each has a separate role in learning, validation, protection or state stabilization.
SETTINGS
ML response controls adaptation speed and signal stability:
Fast reacts sooner.
Balanced is the recommended starting point.
Smooth prioritizes stability.
ML selectivity controls how much evidence is required before LONG or CASH is confirmed.
The indicator is designed exclusively for standard Daily charts.
BUILT-IN COMPARISON
The dashboard includes a lagged long/cash comparison with buy-and-hold. It applies the selected transition cost and openly displays periods when the model underperforms.
This comparison is a diagnostic tool, not a complete strategy backtest. It does not include every possible spread, slippage, tax, financing or execution constraint.
IMPORTANT LIMITATIONS
The bull probability is an internal normalized score, not a statistically calibrated probability of profit. The model can react late, generate false transitions in sideways markets and cannot eliminate gap risk.
The developing Daily bar may change before closing. Confirmed historical states use no future data, no lookahead and no higher-timeframe security calls.
This indicator provides market context, not financial advice or guaranteed performance. Online learning does not imply future outperformance.
Intrabar Profile [Kioseff Trading]Hello Traders!
🔹 Intrabar Profile
Intrabar Profile is a lower-timeframe profile tool designed to draw a volume profile or delta profile on each individual candle .
Instead of only looking at where a candle opened, closed, wicked, or changed color, this indicator attempts to show:
Where did volume actually trade inside the bar?
It focuses on answering a deeper question:
What happened inside the candle that normal candlesticks do not show?
volume profile on every visible bar
delta profile on every visible bar
lower-timeframe volume distribution
POC detection per candle
value area visualization
buy-side vs sell-side imbalance display
optional volume-at-level labels
adaptive scaling as the chart zooms in or out
🔹 What the indicator shows
🔸 Intrabar Volume Profile
The indicator reconstructs a mini volume profile for each candle using lower timeframe data.
This allows you to see:
where volume was concentrated inside each bar
which price level had the highest volume
how volume was distributed across the candle range
whether volume was balanced or concentrated near specific levels
This shifts your perspective from:
“this candle closed bullish or bearish”
to:
“where did participation actually take place inside this candle?”
🔸 POC Per Candle
Each intrabar profile includes a Point of Control , or POC.
The POC marks the price level inside the candle where the highest amount of volume was detected.
This helps identify:
where the most trading activity occurred inside the bar
whether volume was concentrated near the high, low, or middle of the candle
potential areas of intrabar acceptance or rejection
where participation clustered before price moved away
🔸 Value Area Per Candle
The indicator can also display a value area for each profile.
The value area is calculated from total volume and highlights the region where the majority of volume occurred inside the bar.
This helps separate:
high-participation areas
lower-participation areas
balanced candles
thin or inefficient areas of the candle
Together, the POC and value area help show the internal structure of each candle instead of only the candle body and wick.
🔸 Intrabar Delta Profile
Intrabar Profile can also switch from standard volume profile mode to delta profile mode .
Delta mode estimates buy-side and sell-side pressure using lower timeframe price movement and volume.
This allows you to see:
where positive delta appeared inside the candle
where negative delta appeared inside the candle
whether aggressive activity was concentrated at the top, middle, or bottom of the bar
when total volume and directional pressure tell different stories
This can help answer:
Was volume only present, or was it meaningfully skewed toward buyers or sellers?
🔸 Volume Profile vs Delta Profile
The indicator includes two profile modes:
VP - displays total volume distribution inside each candle
Delta - displays directional volume imbalance inside each candle
Volume profile mode focuses on:
where participation occurred
where volume was concentrated
where the candle’s POC and value area formed
Delta profile mode focuses on:
which side had more pressure
where buy-side or sell-side imbalance appeared
whether pressure was distributed evenly or concentrated at specific levels
🔸 Adaptive Mini Profiles
The profiles are drawn directly on top of the chart candles and are designed to stay proportional as the chart is adjusted.
This means the visual structure adapts as you:
zoom in
zoom out
stretch the chart
compress the chart
The goal is to keep the profile readable without turning the chart into visual clutter.
🔹 Granularity Options
The indicator uses lower timeframe data to build each intrabar profile.
Available granularity options include:
5-minute
1-minute
1-second
1-tick
Lower granularity can provide a more detailed reconstruction of intrabar activity, depending on the symbol and data available from PulseWire.
Important Note
Some lower timeframe data options may require specific PulseWire data access or plan availability. If a selected granularity is not available on your chart or account, the indicator can only work with the data PulseWire provides.
🔹 How to read it
Each candle can be read as its own mini profile.
larger profile rows show more volume or stronger absolute delta
the POC marks the highest-volume level inside the candle
the value area highlights the primary participation zone
gray areas show volume outside the selected value area
positive delta shows stronger buy-side pressure
negative delta shows stronger sell-side pressure
This helps you compare:
where the candle closed
where the most volume traded
where delta was strongest
whether the candle’s appearance matches its internal activity
🔹 Example interpretations
bullish candle + volume concentrated near the high → possible acceptance higher
bullish candle + heavy volume near the low → possible absorption or delayed response
bearish candle + negative delta near the low → aggressive selling into the bottom of the bar
large candle + thin profile → fast movement with less balanced participation
small candle + heavy profile → high activity with limited price movement
strong delta but weak candle movement → potential absorption or opposition
🔹 Why this indicator is useful
Intrabar Profile gives you a way to look beyond standard candles.
It helps you see:
where volume formed inside each candle
where the candle’s POC developed
whether participation was concentrated or spread out
whether buyers or sellers dominated specific levels
how volume and delta behaved inside the bar
whether the candle’s structure supports or contradicts the price action
Instead of only asking:
“Did this candle close green or red?”
you can ask:
“Where did the trading actually happen inside this candle?”
🔹 Best use cases
studying intrabar volume structure
analyzing candle quality
identifying high-volume zones inside individual bars
spotting possible absorption or imbalance
comparing price action against internal volume distribution
enhancing volume profile, order flow, or liquidity-based analysis
🔹 Inputs you can customize
profile type: VP or Delta
granularity: 5-minute, 1-minute, 1-second, or 1-tick
number of profile rows
buy-side and sell-side colors
POC color
mini profile transparency
value area visibility
volume-at-level labels
🔹 Important note
This script uses lower timeframe data to approximate intrabar volume and delta structure.
This means:
accuracy depends on available lower timeframe data
different symbols may behave differently
1-second or tick data may not be available for every user or market
delta is estimated from lower timeframe price movement and volume
this is an analytical visualization tool, not a predictive engine
Closing Notes
Intrabar Profile is built to show the internal volume structure of each candle .
It helps turn a normal candlestick chart into a more detailed profile-based view of participation, imbalance, and intrabar activity.
As always, thank you PulseWire!
Kinetic Slippage Index (KSI)Overview
The Kinetic Slippage Index (KSI) is an advanced volume-volatility oscillator designed to measure market efficiency—or the lack thereof. Inspired by order book microstructure and liquidity gaps, KSI calculates the "cost of price movement." It helps traders identify hidden institutional distribution, retail exhaustion, and high-probability false breakouts.
Unlike standard momentum oscillators (RSI, Stochastic) that only track price speed, KSI analyzes how much raw volume was required to achieve a specific price range.
The Theoretical Concept
In a highly liquid and efficient market, large trading volumes are absorbed by dense limit orders, causing the price to move smoothly and tightly.
However, when liquidity clears out (an "empty order book" or "liquidity vacuum"), even a small market order can cause a massive price jump. This phenomenon is called slippage.
KSI mathematically captures this by squaring the True Range and dividing it by the current Volume and its long-term EMA.
- High KSI: Price is flying or dropping fast, but on critically low volume. The market is "hollow."
- Low KSI: Enormous volume is pouring in, but the price is compressed into tight bars. Heavy institutional absorption is taking place.
How to Trade with KSI (Key Use Cases)
1. Fading False Breakouts (The "SPIKE" Signal)
- Scenario: The price breaks out of a key resistance or support level, creating a new local high/low.
- KSI Behavior: A purple "SPIKE" marker appears, meaning KSI has crossed above the critical threshold.
- Interpretation: The breakout is happening on a "hollow" market without institutional backing. It is highly likely a liquidity hunt (stop-run).
- Strategy: Look for a reversal pattern on the price chart and trade against the breakout (Fade).
2. Trend Exhaustion & Climax
- Scenario: The asset is in a strong, prolonged trend. Suddenly, a massive price bar occurs in the direction of the trend.
- KSI Behavior: KSI prints a series of extreme high histogram bars or triggers a "SPIKE" alert.
- Interpretation: This is a buying/selling climax (exhaustion). Smart money is withdrawing their limit orders, letting late retail buyers push the price into a vacuum right before the crash.
- Strategy: Tighten trailing stops on current positions or prepare for a counter-trend setup.
3. Institutional Accumulation / Compression
- Scenario: Price enters a tight, boring consolidation (flat).
- KSI Behavior: The histogram bars turn red and get tightly compressed near the zero line, staying significantly below the orange Signal Line.
- Interpretation: Huge trading volume is being injected, but the price isn't moving. Big players are quietly accumulating or distributing positions using iceberg orders.
- Strategy: Do not trade inside this zone. Prepare for a massive, explosive breakout. Wait for the KSI histogram to flip green and cross above the Signal Line to confirm the direction.
Inputs & Customization
- ATR / Range Period (Default: 14): Controls the lookback window for measuring the price range.
- Volume EMA Period (Default: 20): Smooths out volume to create a reliable benchmark for average liquidity.
- Signal Line Period (Default: 9): An EMA of the KSI itself, used to detect shorter-term shifts in momentum (Green/Red histogram flips).
- Spike Signal Level (Default: 50000): Critical value line for alerts.
Session Edge Profiler | Flux ChartsGENERAL OVERVIEW:
The Session Edge Profiler is a statistical dashboard indicator that profiles up to five configurable trading sessions (Asia, London, NY AM, NY Lunch, NY PM by default) across the available completed trading days loaded on the chart. The indicator records each session's range, volume, directional outcome, and smart money structure (Fair Value Gaps, swing breaks, higher highs, lower lows) on every completed day, then surfaces the resulting statistics in a configurable on-chart dashboard with progress bars and best value markers.
For every metric, the indicator filters history by the selected weekdays. Range-based metrics are normalized against the previous daily ATR for cross-volatility comparison, while volume, directional, extreme, and structure metrics are calculated directly from completed session records. The indicator also computes percentile rankings of the current session range against its historical distribution. Session boxes can be plotted for visual reference, and a live label tracks the active session's running range against its historical average and percentile rank in real time. The indicator is statistical, session based, dashboard driven, and includes one alert condition for sessions exceeding the 90th percentile of their historical range distribution.
WHAT IS THE THEORY BEHIND THE INDICATOR?:
Markets do not move uniformly across the day. Each trading session carries different participant types, different volume profiles, and different structural behaviors. The Asia session tends to be range bound and accumulative. The London session frequently sweeps overnight liquidity. NY AM often produces the largest expansions of the day. NY Lunch is typically the lowest volume window. NY PM frequently reverses or extends NY AM moves into the close.
These tendencies are widely cited but rarely measured per instrument. The Session Edge Profiler quantifies them. By recording per session statistics across the historical window available on the chart, and by filtering by selected weekdays, the indicator builds an empirical profile of how each session has actually behaved on a specific symbol rather than relying on generalized assumptions. The result is a session level statistical profile that can be compared against the current session in real time, identifying when a given session is behaving unusually large, unusually quiet, or consistent with its historical edge.
SESSION EDGE PROFILER FEATURES:
◇ Session tracking with customizable times, names, and colors
◇ Statistical dashboard with up to thirteen configurable metrics
◇ ATR normalized range comparison across sessions
◇ Today percentile ranking of the live session range
◇ Daily extremes tracking (HOD %, LOD %)
◇ Directional statistics (Bull %, Continuation %)
◇ Volume profiling (Vol Share %, Avg Vol)
◇ Smart money structure analytics (FVGs, Swing Breaks, FVG Survival, HH, LL)
◇ Active session live label with real time percentile and average comparison
◇ Session range boxes with current and historical display
◇ Weekday filtering applied uniformly across all statistics
◇ Dashboard theming (Dark or Light), nine position options, and five text sizes
◇ High percentile range alert
SESSION TRACKING AND RANGE BOXES:
🔹What is Session Tracking?
Session Tracking is the foundation of the indicator. Five configurable session windows are monitored on every bar. When price enters a session window, the indicator opens an active tracking object that records the session's high, low, open price, total volume, and structural events. When price leaves the session window, the active object is closed and its values are committed to the historical record for that session.
🔹Why is Session Tracking important?
Every statistic computed by the indicator depends on accurately segmenting the trading day into sessions. Without a reliable session lifecycle, range comparisons, HOD/LOD attribution, volume share, and structure counts would be inconsistent. The session lifecycle also defines what gets drawn on the chart: the live range box for the current session and, optionally, persistent boxes for historical sessions.
🔹How is Session Tracking detected and calculated?
Every bar is checked against the configured session time windows in New York time. The moment price enters a session window, a new session opens: the session's high, low, open, and volume start fresh, and the FVG, swing break, HH, and LL counters reset to zero. While the session is active, the high updates to the running maximum, the low updates to the running minimum, and volume accumulates with each new bar. When price leaves the session window, the session is closed: the final high, low, open, close, and volume are committed and the session is marked complete for the day.
A trading day boundary is determined by shifting time forward by 6 hours and comparing the resulting calendar date in New York time. This shift causes a new day to register at 18:00 NY time, aligning the trading day with the start of the Asia session at 19:00 NY. When a new trading day begins, the completed session statistics from the previous day are added to each session's history along with the weekday they were recorded on, the daily fields reset, and a new tracking cycle begins.
🔹Settings: Sessions Group
◇ Enable Toggle: Turns the session on or off. Disabled sessions are excluded from the dashboard, the live label, and all calculations.
◇ Session Name: Custom label used in the dashboard column header, on the session box, and in the active session label. Defaults: Asia, London, NY AM, NY Lunch, NY PM.
◇ Session Time: The session window in NY time using HHMM,HHMM format. Defaults: Asia 1900,0200, London 0200,0830, NY AM 0830,1200, NY Lunch 1200,1330, NY PM 1330,1600.
◇ Session Color: Color applied to the dashboard column header (when active), the session box border and background, and the active session label.
🔹Customization
Display Group
◇ Show Session Ranges: When enabled, plots a translucent box around the current session showing its running high and low, with the session name labeled in the top left corner. Historical session boxes are also retained on the chart for visual reference.
◇ Show Active Session Stats: When enabled, plots a live label next to the most recent bar of the active session displaying the session name, current range, current range as a percentage of historical average, and current percentile rank.
◇ Label Size: Sets the text size of the active session label. Options: Tiny, Small, Normal, Large, Huge.
STATISTICAL DASHBOARD:
🔹What is the Statistical Dashboard?
The Statistical Dashboard is a configurable table that summarizes the historical statistical profile of every enabled session. Rows correspond to metrics. Columns correspond to sessions. Each cell shows the metric value for that session, optionally rendered with a unicode progress bar and a star marker (★) for the session with the highest value on metrics where "highest" is the meaningful target.
🔹Why is the Statistical Dashboard important?
The dashboard is where the indicator's measurements surface. Rather than requiring a trader to scroll through chart history and visually estimate session behavior, the dashboard reduces the entire weekday filtered history of every session to a compact table of directly comparable numbers. The header line shows the active weekday filter and the maximum number of historical days used in any cell, providing immediate context for the statistical sample size.
🔹How is the Statistical Dashboard calculated?
On the most recent bar of the chart, the indicator reviews each enabled session's stored history. For every past session, it checks whether the weekday it was recorded on is included in the selected weekday filter. If yes, the session contributes to the running totals: range sums, volume sums, HOD/LOD counts, bull counts, continuation counts, FVG counts, swing break counts, HH counts, LL counts, and volume share. After the review, totals are converted to averages or percentages and written to the dashboard cells.
Best value markers are computed by tracking the maximum value across all enabled sessions for the metrics where "highest" is the intended target: Avg Range, HOD %, LOD %, Avg FVGs, and FVG Survival %. For metrics where directional bias matters (Bull %, Continuation %) or where higher is not strictly better (Vol Share %, Avg Swing Breaks, Avg HH, Avg LL), no best marker is shown.
[Screenshot: Full dashboard table screenshot in Dark Mode with every metric row enabled. Header line showing the active weekday filter and sample size, column headers in each session's color, progress bars rendered in percentage cells, and the SMART MONEY divider row visible separating the structural metrics from the range and directional metrics above.
🔹Settings: Dashboard Group
◇ Show Dashboard: Master toggle for the entire dashboard. When disabled, no table is rendered.
◇ Theme: Dark Mode or Light Mode. Controls background, row, header, and text colors. The best value highlight cell uses a deeper accent color on the selected theme.
◇ Position: Table placement on the chart. Options cover all nine combinations of vertical (Top, Middle, Bottom) and horizontal (Left, Center, Right) anchoring.
◇ Text Size: Tiny, Small, Normal, Large, Huge. Affects every cell.
◇ Show Progress Bars: When enabled, percentage and percentile cells render an 8 segment unicode bar alongside the numeric value, scaling from 0% to 100%. When disabled, only the numeric value is shown.
🔹Customization
Metric Toggles
Each of the following dashboard rows can be independently shown or hidden:
◇ Avg Range (ATR%)
◇ Vol Share %
◇ Avg Vol
◇ HOD %
◇ LOD %
◇ Bull %
◇ Continuation %
◇ Today Percentile
◇ Avg FVGs
◇ Avg Swing Breaks
◇ FVG Survival %
◇ Avg HH
◇ Avg LL
🔹Signal Colors
◇ High: Color applied to high tier values (Today Percentile at or above 75, FVG Survival at or above 70). Default: green.
◇ Mid: Color applied to mid tier values (Today Percentile between 25 and 75, FVG Survival between 40 and 70). Default: orange.
◇ Low: Color applied to low tier values (Today Percentile at or below 25, FVG Survival below 40). Default: red.
ATR NORMALIZED RANGE STATISTICS:
🔹What is ATR Normalized Range?
The Avg Range (ATR%) metric expresses each session's average range as a percentage of the daily Average True Range. A value of 45% means the session, on average, covered 45% of a full day's ATR.
🔹Why is ATR Normalized Range important?
Raw range values cannot be compared across instruments or across volatility regimes. A 200 point range means very different things in calm versus volatile markets. Normalizing by daily ATR removes that distortion: the resulting percentage is directly comparable between sessions, between symbols, and between months of history.
🔹How is ATR Normalized Range calculated?
For each completed session, the raw range (session high minus session low) is divided by the daily ATR value of the previous completed day. The daily ATR uses a configurable length (default 14) and is always read from the previous daily bar, which means the value is fixed for the entire current trading day and never repaints. The session's normalized range is stored alongside its weekday in the history. When the dashboard renders, the indicator averages all normalized ranges from sessions whose weekday passes the filter, then multiplies by 100 to produce the displayed percentage.
🔹What is Today Percentile?
Today Percentile expresses where the current session's live range sits within the historical distribution of that same session's past ranges. The comparison stays within the session: today's London is compared only against past Londons, today's NY AM only against past NY AMs, and so on, all filtered by the selected weekdays. A value of 80 means the live range is larger than 80% of past occurrences of the same session on those weekdays.
🔹How is Today Percentile calculated?
For each enabled session, the indicator computes the current normalized range (current session range divided by daily ATR). It then walks through that session's own past history, counting how many past sessions have a normalized range less than or equal to the current value, while skipping any past session whose weekday is not enabled in the filter. The percentile is the percentage of qualifying past sessions at or below the current value.
The cell color reflects the tier: at or above 75 uses the High color, at or below 25 uses the Low color, otherwise the Mid color. The numeric value is rendered with an ordinal suffix (1st, 2nd, 3rd, 4th, and so on) for readability, and the progress bar segments scale from 0 to 100.
🔹Settings:Filters Group
◇ ATR Length: Lookback for the daily ATR used in normalization. Range: 5 to 50. Default: 14.
DAILY EXTREMES TRACKING:
🔹What are HOD % and LOD %?
HOD % measures how often a given session contained the day's highest price. LOD % measures how often it contained the day's lowest price. Both are expressed as a percentage of the total weekday filtered days in history.
🔹Why are HOD/LOD statistics important?
Knowing which session historically sets the daily extreme on a given instrument helps frame intraday liquidity expectations. A session with a high HOD % is the session that most frequently posts the day's selling extreme. A session with a high LOD % most frequently posts the day's buying extreme. On many instruments NY AM dominates both, but the ratio shifts by symbol and by weekday, which is why measuring rather than assuming is useful.
🔹How are HOD % and LOD % calculated?
While the trading day is in progress, the indicator continuously tracks the day's running high and running low across all bars, not just within session windows. When a new trading day begins, every completed session from the previous day is checked: if the session's recorded high matches the day's high, that session is tagged as the HOD session; if its low matches the day's low, it is tagged as the LOD session. These tags are stored with the session in history. When the dashboard renders, it counts how many sessions in the weekday filtered history carry each tag and converts those counts to percentages. The session with the highest HOD % across all enabled sessions receives a star marker, and the same applies to LOD %.
DIRECTIONAL STATISTICS:
🔹What are Bull % and Continuation %?
Bull % is the percentage of historical sessions that closed higher than they opened. Continuation % is the percentage of historical sessions whose direction matched the previous occurrence of the same session.
🔹Why are directional statistics important?
Bull % captures the session's directional skew. A session with Bull % consistently above 60% on a particular instrument and weekday set has a measurable upward tendency. Continuation % captures the session's persistence: a high continuation rate means the session frequently extends the previous day's same session direction, while a low rate suggests the session tends to reverse the prior day's bias.
🔹How are Bull % and Continuation % calculated?
For each completed session, Bull is true when the session's close (the chart close at the bar where the session ended) exceeds its open. Continuation is true when the previous occurrence of the same session was bullish in the same direction (both bullish or both bearish). The very first occurrence in history has no previous reference and is excluded from the continuation calculation. The dashboard divides the bullish session count by the total session count for Bull %, and the matched continuation count by the continuation eligible count for Continuation %.
No best value marker is shown for either metric, since "highest" is not inherently better: directional bias and continuation are interpretive measurements rather than competitive ones across sessions.
VOLUME PROFILING:
🔹What is Volume Profiling?
The indicator tracks two volume metrics per session: Vol Share % (the session's average share of total daily volume) and Avg Vol (the session's average absolute volume).
🔹Why is Volume Profiling important?
Volume distribution across the day reveals participant activity. Sessions that historically account for a disproportionate share of daily volume are the sessions where flow is most concentrated. Sessions with low volume share (typically NY Lunch) are statistical low conviction windows where moves are more likely to be lower quality.
🔹How is Volume Profiling calculated?
While each session is active, the indicator accumulates bar volume into the session's running total. When the trading day rolls over, total day volume is computed as the sum of all completed session volumes for that day. Each session's Vol Share is then computed as its session volume divided by total day volume, multiplied by 100, and saved into the session's history alongside the absolute volume. When the dashboard renders, Avg Vol is the simple weekday filtered mean of recorded session volumes, and Vol Share % is averaged across the weekday filtered history.
SMART MONEY STRUCTURE ANALYTICS:
🔹What are the Smart Money metrics?
The Smart Money section of the dashboard surfaces four structural counters per session:
◇ Avg FVGs: average number of Fair Value Gaps formed during the session.
◇ Avg Swing Breaks: average instances where the close pierces a previously confirmed pivot high or pivot low.
◇ FVG Survival %: percentage of FVGs that were not invalidated within the same session in which they formed.
◇ Avg HH and Avg LL: average count of new higher highs and lower lows in pivot structure during the session.
🔹Why are Smart Money metrics important?
These metrics quantify the structural activity of each session. High FVG counts indicate aggressive displacement and gap creation. High Swing Break counts indicate liquidity sweeps and structural inflection. FVG Survival measures how often gaps formed during the session are respected (not immediately filled in the opposite direction), giving a session level reliability score for the FVG concept. HH and LL counts profile each session's tendency to extend structure in one direction versus the other.
🔹How are Smart Money metrics calculated?
A Fair Value Gap is detected as a 3 bar pattern: a bullish FVG forms when the current bar's low sits above the high from two bars ago, and a bearish FVG forms when the current bar's high sits below the low from two bars ago. Whenever an FVG forms during an active session, the session's FVG counter increments and the gap level (the high from two bars ago for a bullish FVG, the low from two bars ago for a bearish FVG) is added to a list of active gaps for that session, along with its direction.
On every later bar within the same session, the indicator checks each active gap. If price closes below a bullish FVG's level, or closes above a bearish FVG's level, the gap is treated as invalidated and removed from the active list, and the session's invalidation counter increments. At the end of the session, FVG Survival % is computed as the count of total FVGs minus invalidated FVGs, divided by total FVGs, expressed as a percentage. The cell is color coded by tier: at or above 70 uses High, at or above 40 uses Mid, otherwise Low.
Swing breaks use a configurable pivot strength (default 5 bars on each side). When a pivot high confirms and price subsequently closes above that pivot level, a bullish swing break fires and the pivot is consumed (cleared from active tracking). The same applies symmetrically for pivot lows. Each break increments the active session's Swing Break counter.
HH and LL counts use the same pivot detection. When a new pivot high confirms with a level greater than the session's previous tracked pivot high, the session's HH counter increments. When a new pivot low confirms with a level less than the session's previous tracked pivot low, the LL counter increments.
🔹Settings: Filters Group
◇ Pivot Strength: Bars on each side required to confirm a pivot high or pivot low for the swing break and HH/LL calculations. Range: 2 to 20. Default: 5. Higher values produce fewer, more significant pivots; lower values produce more frequent, noisier pivots.
ACTIVE SESSION LIVE LABEL:
🔹What is the Active Session Live Label?
A floating label that appears next to the most recent bar of the active session, displaying live statistics for the session currently in progress.
🔹Why is the Active Session Live Label important?
The dashboard summarizes completed historical sessions. The live label answers a different question: how does the session that is currently developing compare to history, right now? It allows a trader to see, mid session, whether the current session is tracking above, near, or below its average range and what percentile it currently occupies, without waiting for the session to close.
🔹How is the Active Session Live Label calculated?
The label content includes the session name, the live range (current session high minus current session low), the ratio of the live normalized range to the historical average normalized range expressed as a percentage, and the current percentile. The percentile is computed by iterating the session's weekday filtered history and counting how many records have a normalized range at or below the live value.
The label position updates every bar to track the right edge of the current session at its current high. When the session ends, the label is deleted.
🔹Settings
◇ Show Active Session Stats: Toggle for the label.
◇ Label Size: Sets text size. Options: Tiny, Small, Normal, Large, Huge.
WEEKDAY FILTERING:
🔹What is Weekday Filtering?
A set of seven toggles (Sunday through Saturday) that determines which weekdays contribute to every statistic on the dashboard, the live label, and the alert condition.
🔹Why is Weekday Filtering important?
Session behavior is not uniform across the week. Monday open behavior differs from midweek behavior. Friday afternoon often shows reduced participation. By filtering history to only the selected weekdays, traders can profile each session under conditions that match the current trading day, rather than averaging in unrelated days.
🔹How is Weekday Filtering applied?
Each session in history is tagged with the weekday it was recorded on. Every calculation in the dashboard, the live label, and the alert checks that weekday against the user's selection and skips any session whose weekday is not enabled. The dashboard header line displays a compact label of the active filter: "All" when every weekday is enabled, "Weekdays" when only Monday through Friday are enabled, or a custom combination such as "M/Tu/W" otherwise. The header also shows the largest number of sessions any column was able to use after filtering, which serves as the sample size indicator.
🔹Settings: Filters Group
◇ Sun, Mon, Tue, Wed, Thu, Fri, Sat: Individual toggles. Defaults: Mon, Tue, Wed, Thu, Fri enabled; Sun and Sat disabled.
ALERTS:
🔹What alerts are available?
A single alert condition is provided:
◇ Range > 90th Percentile: Fires when an active session's current normalized range exceeds the 90th percentile of its weekday filtered historical normalized range distribution.
🔹When does it fire?
On every bar where at least one enabled, active session has a current normalized range above which 90% of its history sits. The alert fires once per qualifying bar, allowing traders to be notified when a session is in the process of becoming statistically large relative to its own history.
IMPORTANT NOTES:
◇ All session times are evaluated in New York time regardless of the chart's display timezone. Adjust session times if profiling instruments where session timing conventions differ from the defaults.
◇ Trading day boundaries are anchored to 18:00 NY time (the 6 hour shift before midnight) so that the Asia session opens at the start of each new trading day. This is the convention used for HOD/LOD attribution and for pushing completed session records to history.
◇ Daily ATR is always read from the previous completed daily bar. This means the value used for normalization is fixed for the current trading day and does not repaint as new bars print, while still giving the live percentile calculations a stable reference.
◇ The session history for each session is built progressively as the chart loads. Sessions on the very first day on the chart cannot contribute to continuation statistics because no earlier occurrence of the same session exists to compare against.
◇ Best value markers (★) are shown only on metrics where "highest" is the meaningful target: Avg Range, HOD %, LOD %, Avg FVGs, and FVG Survival %. Other metrics intentionally omit the marker.
◇ FVG Survival counts gaps that survive to the end of the session in which they formed. A gap that survives the session but is invalidated on a later day is still counted as survived for the session that created it.
UNIQUENESS:
The Session Edge Profiler distinguishes itself from common session indicators in several ways. Most session tools plot boxes and stop there, while this indicator extends session tracking into a full statistical profile with thirteen configurable metrics per session, reducing the entire history of every session to a single, scannable table. Range comparisons use ATR normalization rather than raw point values, making the dashboard meaningful across volatility regimes and instruments without per chart recalibration, and percentile ranking of the live session against history provides a single number answer to a question many traders ask intuitively: is this session unusually large or unusually small for this time and this weekday? FVG and swing break tracking are integrated into the session profile rather than treated as separate indicators, allowing direct comparison of which session produces the most structural activity and how reliable that structure tends to be on a given instrument. FVG Survival % quantifies a concept that is rarely measured anywhere else: how often each session's FVGs actually hold within their own session, converting a qualitative idea into a session level reliability score. Weekday filtering applies uniformly to every statistic on the dashboard, the live label, and the alert, allowing traders to profile sessions only on days that match the current trading day rather than diluting the sample with unrelated weekdays. Best value markers and progress bars make the dashboard scannable at a glance, with the strongest session per metric immediately visible without parsing numbers. Finally, the active session live label provides real time positional context that complements the historical dashboard: the dashboard answers what a session usually does, while the label answers what the session is doing right now, with both views driven by the same underlying statistical model.
Supertrend Parameter Sensitivity 3D [LuxAlgo]The Supertrend Parameter Sensitivity 3D indicator is a powerful optimization tool that executes 100 simultaneous Supertrend backtests bar-by-bar to visualize how different ATR Lengths and Multipliers impact performance across various metrics.
By projecting this data onto a 3D surface and a heatmap dashboard, it allows traders to identify "stable" parameter zones and avoid over-optimized "peaks" that may lead to curve-fitting.
🔶 USAGE
This tool is designed to help traders find the most robust settings for the Supertrend indicator on any given timeframe or asset. Instead of manually guessing settings, users can see a holistic view of the parameter space.
🔹 3D Surface Projection
The 3D surface is rendered directly on the chart, where the X-axis represents the Multiplier, the Y-axis represents the ATR Length, and the Z-axis (height) represents the chosen performance metric.
Gold Highlight: Marks the absolute "Best" parameter combination based on the selected metric.
Blue Highlight: Marks the "Stable Area," which is the region where the average performance of a 3x3 parameter window is highest. This helps identify settings that remain profitable even if market conditions shift slightly.
🔹 Optimization Dashboard
The dashboard provides a detailed heatmap of the 100 tested combinations.
Value Distribution: An ASCII histogram at the top shows the distribution of all results, helping you understand if the "best" setting is an outlier or part of a consistent trend.
Heatmap Matrix: Displays the exact values for every combination. Hovering over any cell in the table reveals a tooltip with specific data, including the total number of trades for that combination.
Color Scaling: The colors are normalized relatively. Green represents the best results in the current set, while red represents the worst, allowing for clear visual distinction even if all results are negative or positive.
🔶 DETAILS
🔹 Bar-by-Bar Evaluation
The script manages 100 independent Supertrend states simultaneously. On every bar, it calculates the ATR and trailing stop levels for every combination in the sensitivity matrix. It simulates "Always-in-Market" trades (flipping long/short on direction changes) to track performance data without needing a separate strategy execution.
🔹 Optimization Metrics
Users can choose from 9 different metrics to optimize the 3D surface and Dashboard:
Win Rate: Percentage of trades that resulted in a profit.
Net Profit: Total gross profit minus total gross loss.
Profit Factor: Ratio of gross profit to gross loss.
Total Trades: The raw volume of signals generated.
Average Trade: The mean percentage return per trade.
Reward/Risk Ratio: The average win divided by the average loss.
Gross Profit: Total sum of all winning trades.
Total Wins: The absolute count of profitable trades.
Win/Loss Ratio: The count of wins divided by the count of losses.
🔶 SETTINGS
🔹 Main Indicator
ATR Length: The length used for the primary Supertrend line plotted on the chart.
Multiplier: The multiplier used for the primary Supertrend line plotted on the chart.
🔹 Sensitivity Ranges
Length Start: The starting ATR length for the 10x10 matrix.
Length Step: The increment added to the length for each subsequent row.
Multiplier Start: The starting Multiplier for the 10x10 matrix.
Multiplier Step: The increment added to the multiplier for each subsequent column.
🔹 Optimization
Metric: Selects the performance data used to determine the Z-height of the surface and the colors of the heatmap.
🔹 3D Surface Style
High/Low/Wire/Stable Colors: Customize the visual appearance of the 3D projection.
X/Y/Z Spacing & Scale: Adjusts the physical dimensions and height of the 3D surface on the chart.
🔹 Dashboard
Enable Dashboard: Toggles the visibility of the heatmap table.
Position/Size: Controls where the dashboard appears and how large it is on the screen.
ExprLibExprLib is a library for parsing and evaluating string expressions. It allows scripts to expose configurable logic by letting users define custom conditions and calculations based on available data.
█ KEY FEATURES
• Rich expression support:
• Built-in constants (e.g., `10`, `2.5`, `5e-2`, `true`, `false`, `na`)
• Custom constants
• Variables
• Arithmetic operators: `+`, `-`, `*`, `/`, `%`
• Comparison operators: `>`, `<`, `>=`, `<=`, `==`, `!=`
• Logical operators: `AND`, `OR`, `NOT` (with aliases)
• Ternary operator: `condition ? if_true : if_false`
• Parentheses: `(`, `)`
• Built-in functions: `na()`, `nz()`, `max()`, `pow()`, `sqrt()`, `random()`, and more!
• Graceful error handling during parsing and evaluation
• Optimized for evaluation performance (RPN-based approach)
█ NOTE
Since the library description cannot be changed or removed after publication, some information here may be outdated. However, you can always get the latest version of the documentation at the bottom of the source code.
█ QUICK START
An example of an indicator that colors areas on a chart where the expression evaluates to `true`:
//@version=6
indicator("Quick Start", overlay = true)
import A1trdX/ExprLib/1 as ExprLib
// ---------------
// INPUTS
// ---------------
// Let the user customize the expression
inputExpressionStr = input.text_area("trend_up AND (rsi < 50 OR close < open)", "Expression")
// -------------------
// CALCULATION
// -------------------
// Prepare some data to use in the expression.
rsi = ta.rsi(close, 14)
ema = ta.ema(close, 200)
isTrendUp = close > ema
isTrendDown = close < ema
// Step 0: Prepare the parser and evaluator.
var parser = ExprLib.createExpressionParser()
var evaluator = ExprLib.createExpressionEvaluator()
// Step 1: Parse the expression string.
var expression = parser.parse(inputExpressionStr)
// Step 2 (Recommended): Verify whether the expression was parsed without errors.
if not parser.isParsed
// You can define your own logic to handle errors
runtime.error("Failed to parse expression: " + parser.error.message)
// Step 3: Assign values to variables. Both numbers and booleans are supported.
expression.setVariable("open", open)
expression.setVariable("close", close)
expression.setVariable("rsi", rsi)
expression.setVariable("trend_up", isTrendUp)
expression.setVariable("trend_down", isTrendDown)
// Step 4: Evaluate the expression.
bool result = evaluator.evaluateToBool(expression)
// Step 4 (Alternative): If you expect a numeric result, use `evaluate()` instead.
// float result = evaluator.evaluate(expression)
// Step 5 (Recommended): Verify whether the expression was evaluated without errors.
if not evaluator.isEvaluated
// You can define your own logic to handle errors
runtime.error("Failed to evaluate expression: " + evaluator.error.message)
// ----------------
// GRAPHICS
// ----------------
// Highlight bars where the expression returns `true`
bgcolor(result ? color.new(color.green, 90) : na)
█ EXPRESSION SYNTAX REFERENCE
❱❱ Components
An expression can include:
• Constants
• Variables
• Operators
• Functions
• Parentheses
• Spaces, tabs, or newlines
❱❱ Data Types
Constants and variables can have the following data types:
• Numeric (`int`, `float`)
• Boolean (`bool`)
• Undefined (`na`)
❱❱ Identifiers
Identifiers are names used to refer to named constants, variables, and functions.
Identifier naming rules:
• Must start with a letter (`a-z`, `A-Z`) or underscore (`_`).
• May contain letters (`a-z`, `A-Z`), digits (`0-9`), and underscores (`_`).
Identifiers cannot contain spaces or other characters.
Identifiers are case-sensitive.
❱❱ Constants
Numeric Constants
Examples:
+-----------+--------------+
| Constant | Plain Value |
+-----------+--------------+
| 12 | 12.00 |
| 0.05 | 0.05 |
| .05 | 0.05 |
| 5e-2 | 0.05 |
| 5E-2 | 0.05 |
| 1.2e4 | 12000.00 |
+-----------+--------------+
Named Constants
Available built-in named constants:
+----------+-------------------------------------+-------------------------+
| Name | Description | Pine Script Equivalent |
+----------+-------------------------------------+-------------------------+
| `true` | Boolean TRUE | `true` |
| `false` | Boolean FALSE | `false` |
| `na` | Undefined value | `na` |
| `pi` | Pi (~3.14159) | `math.pi` |
| `e` | Euler's number (~2.71828) | `math.e` |
| `phi` | Golden ratio (~1.61803) | `math.phi` |
| `rphi` | Golden ratio conjugate (~0.61803) | `math.rphi` |
+----------+-------------------------------------+-------------------------+
It is possible to add custom constants.
❱❱ Variables
It is possible to add variables, just like custom constants, except that variable values can be changed before each evaluation.
❱❱ Operators
The following operators are supported:
+--------------+-------------+-------------------------+-------------+------------------+-------------+
| Type | Operator | Name | Aliases | Example #1 | Example #2 |
+--------------+-------------+-------------------------+-------------+------------------+-------------+
| Arithmetic | `+` | Add | | `a + b` | |
| Arithmetic | `-` | Subtract | | `a - b` | |
| Arithmetic | `*` | Multiply | | `a * b` | |
| Arithmetic | `/` | Divide | | `a / b` | |
| Arithmetic | `%` | Modulo | | `a % b` | |
| Comparison | `>` | Greater than | | `a > b` | |
| Comparison | `<` | Less than | | `a < b` | |
| Comparison | `>=` | Greater than or equal | | `a >= b` | |
| Comparison | `<=` | Less than or equal | | `a <= b` | |
| Comparison | `==` | Equal | | `a == b` | |
| Comparison | `!=` | Not equal | | `a != b` | |
| Logical | `AND` | Logical AND | `&&`, `&` | `a AND b` | `a && b` |
| Logical | `OR` | Logical OR | `||`, `|` | `a OR b` | `a || b` |
| Logical | `NOT` | Logical NOT | `!` | `NOT x` | `!x` |
| Conditional | `?:` | Ternary | | `cond ? x : y` | |
| Unary | Unary `+` | Unary plus | | `+x` | |
| Unary | Unary `-` | Unary minus | | `-x` | |
+--------------+-------------+-------------------------+-------------+------------------+-------------+
Logical operator names are case-insensitive.
Operator precedence:
+------------+-----------------------------+
| Precedence | Operators |
+------------+-----------------------------+
| 8 | Unary `-`, Unary `+`, `NOT` |
| 7 | `*`, `/`, `%` |
| 6 | `+`, `-` |
| 5 | `>`, `<`, `>=`, `<=` |
| 4 | `==`, `!=` |
| 3 | `AND` |
| 2 | `OR` |
| 1 | `?:` |
+------------+-----------------------------+
Operator associativity:
• Unary `+`, Unary `-`, `NOT`, and ternary are right-associative
• Other operators are left-associative
❱❱ Parentheses
Parentheses are used to group sub-expressions and override the default operator precedence.
Example:
((a + b) * c + 1) * d
❱❱ Functions
Functions are called by an identifier followed immediately by parentheses: `func(arg1, arg2)`.
Arguments are separated by commas. Each argument can be any valid expression, including another function call.
Available built-in functions:
+-------------------------------+----------+------------------------------------------------------------------------+
| Function | Args | Description |
+-------------------------------+----------+------------------------------------------------------------------------+
| `na(x)` | 1 | Returns `true` when `x` is `na`, `false` otherwise. |
| `nz(x, fallback)` | 2 | Returns `x` when it is not `na`, `fallback` otherwise. |
| `max(x1, x2, ...)` | 2..999 | Returns the largest argument. |
| `min(x1, x2, ...)` | 2..999 | Returns the smallest argument. |
| `pow(base, exponent)` | 2 | Returns `base` raised to `exponent`. |
| `sqrt(x)` | 1 | Returns the square root of `x`. |
| `clamp(x, min, max)` | 3 | Restricts `x` to the ` ` range. |
| `abs(x)` | 1 | Returns the absolute value of `x`. |
| `ceil(x)` | 1 | Rounds `x` up to the nearest integer. |
| `floor(x)` | 1 | Rounds `x` down to the nearest integer. |
| `round(x)` | 1 | Rounds `x` to the nearest integer. |
| `round_to_mintick(x)` | 1 | Rounds `x` to the symbol's minimum tick precision. |
| `log(x)` | 1 | Returns the natural logarithm of `x`. |
| `log10(x)` | 1 | Returns the base-10 logarithm of `x`. |
| `sign(x)` | 1 | Returns the sign of `x`: `1`, `0`, or `-1`. |
| `cos(x)` | 1 | Returns the cosine of `x` in radians. |
| `sin(x)` | 1 | Returns the sine of `x` in radians. |
| `tan(x)` | 1 | Returns the tangent of `x` in radians. |
| `acos(x)` | 1 | Returns the arccosine of `x` in radians. |
| `asin(x)` | 1 | Returns the arcsine of `x` in radians. |
| `atan(x)` | 1 | Returns the arctangent of `x` in radians. |
| `deg(x)` | 1 | Converts radians to degrees. |
| `rad(x)` | 1 | Converts degrees to radians. |
| `random(min, max, seed)` | 0..3 | Returns a random float. Bounds default to 0 and 1. Seed is optional. |
| `random_int(min, max, seed)` | 2..3 | Returns a random integer. Seed is optional. |
| `random_bool(seed)` | 0..1 | Returns a random boolean value. Seed is optional. |
+-------------------------------+----------+------------------------------------------------------------------------+
The number of arguments can be either fixed or variable.
For example, the `max(x1, x2, ...)` function supports 2 to 999 arguments, so the following calls to this function are valid:
max(x1, x2)
max(x1, x2, x3)
max(x1, x2, x3, x4, x5)
Other functions may have optional arguments. For example, the following calls to the `random(min, max, seed)` function are valid:
random() // Random float from 0 to 1
random(0.5) // Random float from 0.5 to 1
random(0.5, 2) // Random float from 0.5 to 2
random(0.5, 2, 777) // Random float from 0.5 to 2 with a specific seed
❱❱ Whitespace
Spaces, tabs, and line breaks are ignored between symbols. For example, an expression can be formatted across multiple lines:
price > ema_slow
AND ema_fast > ema_slow
AND (bb_lo_up OR rsi_lo_up)
█ PARSING
❱❱ Workflow
Before evaluating an expression, it must be parsed. To do this:
• Create a parser in advance using the `createExpressionParser()` function.
• Call the `parse()` method, passing the expression string as an argument.
Example:
var parser = ExprLib.createExpressionParser()
var expr1 = parser.parse("a + 2")
var expr2 = parser.parse("a + b * c")
❱❱ Error Handling
A user may enter an invalid expression. In this case, the parser will return `na` instead of a valid expression object. The parser stores the result of the last parse. You can use that result to retrieve the status and error information.
Parser and error field structures:
type ExpressionParser
bool isParsed // `true` if the last parse completed successfully, `false` otherwise.
ParseError error // Error from the last parse attempt. If the last parse was successful, then this field is `na`.
type ParseError
string message // Error message.
int index // Character index where the parser detected the error.
For example, suppose we want to display an error message on the chart if one of the expressions is invalid:
//@version=6
indicator("Parser Error Handling")
import A1trdX/ExprLib/1 as ExprLib
inputExpr1 = input.text_area("a + 2", "Expression 1")
inputExpr2 = input.text_area("a + b * c /", "Expression 2")
displayErrorMessage(string errorMessage) =>
var table errorMessageTable = na
if na(errorMessageTable)
errorMessageTable := table.new(position.top_right, 1, 1)
errorMessageTable.cell(0, 0, errorMessage,
bgcolor = color.red,
text_color = color.white,
text_halign = text.align_left,
text_formatting = text.format_bold)
checkParsed(ExprLib.ExpressionParser parser, string prefix) =>
if not parser.isParsed
displayErrorMessage(prefix + parser.error.message)
var parser = ExprLib.createExpressionParser()
var expr1 = parser.parse(inputExpr1)
checkParsed(parser, "Failed to parse expression #1: ")
var expr2 = parser.parse(inputExpr2)
checkParsed(parser, "Failed to parse expression #2: ")
A blank expression (e.g., "") is allowed and will evaluate to `na` (or `false` when returning a boolean value).
❱❱ Custom Constants
You can add your own named constants during the parsing stage. To do this:
• Create a constant pool in advance using the `createConstantPool()` function.
• Set constants and their values using the `set()` method.
• Pass the constant pool to the `parse()` method.
Example:
var constantPool = ExprLib.createConstantPool()
if barstate.isfirst
constantPool.set("one", 1)
constantPool.set("two", 2)
constantPool.set("three_p_one", 3.1)
constantPool.set("yes", true)
constantPool.set("no", false)
var parser = ExprLib.createExpressionParser()
var expr = parser.parse("one + two", constantPool)
The `set()` method returns the same constant pool object, so you can chain calls together. This is more convenient and more elegant:
var constantPool = ExprLib.createConstantPool()
.set("one", 1)
.set("two", 2)
.set("three_p_one", 3.1)
.set("yes", true)
.set("no", false) // Note that the indentation is 7 spaces (not a multiple of 4)
var parser = ExprLib.createExpressionParser()
var expr = parser.parse("one + two", constantPool)
You can also override built-in constants:
var constantPool = ExprLib.createConstantPool()
.set("true", false)
.set("false", -1)
.set("na", 0.0)
█ EVALUATION
❱❱ Type Coercion
An expression can consist of values of different data types. ExprLib does not have strict data type checking. Instead, all values are converted to `float` and then back if necessary.
Converting `bool` to `float`:
• `true` -> `1.0`
• `false` -> `0.0`
Converting `float` to `bool`:
• `0.0` or `na` -> `false`
• Any other value -> `true`
Thus, expressions that incorrectly combine different data types are allowed. For example, `true + 2` will return `3.0`. Strict typing requires additional memory as well as additional computational resources during evaluation, which is a critical concern. Therefore, it was decided not to implement it.
As in Pine Script, most operations with an `na` operand results in `na` or `false`, but logical operations first convert `na` to `false`, so their result follows boolean logic. For example:
• `3 - na` returns `na`
• `3 > na` returns `false`
• `3 <= na` also returns `false`
• `na AND true` returns `false`
• `na OR true` returns `true`
• `NOT na` returns `true`
❱❱ Workflow
To evaluate an expression:
• Create an evaluator in advance using the `createExpressionEvaluator()` function.
• Set variables and their values in the expression using the `setVariable()` method.
• Call the `evaluate()` or `evaluateToBool()` method, passing the expression as an argument.
The `evaluate()` and `evaluateToBool()` methods differ in their return types. The former returns a `float` result, while the latter returns a `bool` result. The method to call depends on the expected result type.
Example:
// Parsed expressions:
// - expr1 <= "(H - L) / 2 + L"
// - expr2 <= "rsi_oversold AND close > open"
// Initialize evaluator
var evaluator = ExprLib.createExpressionEvaluator()
// Set variables and evaluate the first expression
expr1.setVariable("H", high)
expr1.setVariable("L", low)
float result1 = evaluator.evaluate(expr1)
// Set variables and evaluate the second expression
rsi = ta.rsi(close, 14)
expr2.setVariable("open", open)
expr2.setVariable("close", close)
expr2.setVariable("rsi_oversold", rsi < 30)
expr2.setVariable("rsi_overbought", rsi > 70)
bool result2 = evaluator.evaluateToBool(expr2)
❱❱ Variables
If an expression contains an identifier that is neither a function nor a constant, and this identifier has not been assigned a variable value, then this identifier is considered a constant with the value `na` (or `false` in boolean operations).
The `setVariable()` method overrides existing constants (both built-in and custom). For example, by default, the identifier `e` is used as the constant Euler's number (~2.71828). However, you can make `e` your own variable:
// Parsed expressions:
// - expr <= "e + 1"
expr.setVariable("e", 5) // Now `e` is equal to `5` instead of `2.7182818284590452`
result = evaluator.evaluate(expr) // `6.0`
The `setVariable()` method does not need to be called on each bar if the variable's value does not change. The expression always stores and uses the last value set.
You can clear all previously set variables using the `clearVariables()` method. This can be useful if you have many variables and want to reset them all and set values for only a small subset.
❱❱ Error Handling
In some cases (for example, when dividing by zero), evaluation results in an error. In this case, `evaluate()` will return `na`, and `evaluateToBool()` will return `false`. Like the parser, the evaluator stores the result of the last evaluation.
Evaluator and error field structures:
type ExpressionEvaluator
bool isEvaluated // `true` if the last evaluation completed successfully, `false` otherwise.
EvaluationError error // Error from the last evaluation attempt. If the last evaluation was successful, then this field is `na`.
type EvaluationError
EvaluationErrorReason reason // Error reason.
string message // Error message.
enum EvaluationErrorReason
DIVISION_BY_ZERO
Example:
//@version=6
indicator("Evaluator Error Handling")
import A1trdX/ExprLib/1 as ExprLib
inputExpr1 = input.text_area("a + 2", "Expression 1")
inputExpr2 = input.text_area("a + b / c", "Expression 2")
displayErrorMessage(string errorMessage) =>
var table errorMessageTable = na
if na(errorMessageTable)
errorMessageTable := table.new(position.top_right, 1, 1)
errorMessageTable.cell(0, 0, errorMessage,
bgcolor = color.red,
text_color = color.white,
text_halign = text.align_left,
text_formatting = text.format_bold)
// Parse
checkParsed(ExprLib.ExpressionParser parser, string prefix) =>
if not parser.isParsed
displayErrorMessage(prefix + parser.error.message)
var parser = ExprLib.createExpressionParser()
var expr1 = parser.parse(inputExpr1)
checkParsed(parser, "Failed to parse expression #1: ")
var expr2 = parser.parse(inputExpr2)
checkParsed(parser, "Failed to parse expression #2: ")
// Evaluate
checkEvaluated(ExprLib.ExpressionEvaluator evaluator, string prefix) =>
if not evaluator.isEvaluated
displayErrorMessage(prefix + evaluator.error.message)
var evaluator = ExprLib.createExpressionEvaluator()
expr1.setVariable("a", open)
expr1.setVariable("b", close)
expr1.setVariable("c", 0)
result1 = evaluator.evaluate(expr1)
checkEvaluated(evaluator, "Failed to evaluate expression #1: ")
expr2.setVariable("a", open)
expr2.setVariable("b", close)
expr2.setVariable("c", 0)
result2 = evaluator.evaluate(expr2)
checkEvaluated(evaluator, "Failed to evaluate expression #2: ")
Currently, the only possible cause of this error is division by zero. You can disable this error and have the evaluator interpret the result of division by zero as `na`. To do this, disable the corresponding flag in the evaluator:
evaluator.setFailOnDivisionByZero(false)
Thus, an expression like `na(5 / 0) ? 1 : 2` will return `1` instead of an error.
█ BEST PRACTICES
• Reuse `ExpressionParser` and `ExpressionEvaluator` objects whenever possible.
• Parse expressions only once, and evaluate them as needed. Parsing is slow. Evaluation is fast.
• If certain variable values change rarely, call `setVariable()` only when necessary.
• Try to avoid excessive numbers of variables whose values change frequently. This can impact performance even if they're not used in the expression.
█ API REFERENCE
❱❱ Expression Parser
ExpressionParser
Expression parser.
Fields:
isParsed (series bool) : `true` if the last parse completed successfully, `false` otherwise.
error (ParseError) : Error from the last parse attempt. If the last parse was successful, then this field is `na`.
createExpressionParser()
Creates an expression parser.
Returns: Expression parser.
method parse(parser, exprStr, constantPool)
Parses an expression.
Namespace types: ExpressionParser
Parameters:
parser (ExpressionParser) : Expression parser.
exprStr (string) : Expression string. Can be empty, blank, or 'na'. That way expression is valid and will return `na` on evaluation.
constantPool (ExpressionConstantPool) : (Optional) Named constants.
Returns: Parsed expression. If an error occurs during parsing, then the returned expression will be `na`.
You can check validity and error details accessing parser's `isParsed` and `error` fields.
❱❱ Expression
Expression
Parsed expression.
method setVariable(expr, identifier, value)
Assigns a numeric value to a variable.
Namespace types: Expression
Parameters:
expr (Expression) : Expression.
identifier (string) : Variable name.
value (float) : Value.
Returns: This expression.
method setVariable(expr, identifier, value)
Assigns a boolean value to a variable.
Namespace types: Expression
Parameters:
expr (Expression) : Expression.
identifier (string) : Variable name.
value (bool) : Value.
Returns: This expression.
method clearVariables(expr)
Clears all variable values.
Namespace types: Expression
Parameters:
expr (Expression) : Expression.
Returns: This expression.
❱❱ Constant Pool
ExpressionConstantPool
Expression constant pool.
createConstantPool()
Creates an expression constant pool.
Returns: Expression constant pool.
method set(pool, identifier, value)
Assigns a numeric constant value.
Namespace types: ExpressionConstantPool
Parameters:
pool (ExpressionConstantPool) : Expression constant pool.
identifier (string) : Constant name.
value (float) : Value.
Returns: This expression constant pool.
method set(pool, identifier, value)
Assigns a boolean constant value.
Namespace types: ExpressionConstantPool
Parameters:
pool (ExpressionConstantPool) : Expression constant pool.
identifier (string) : Constant name.
value (bool) : Value.
Returns: This expression constant pool.
method clear(pool)
Clears all constants.
Namespace types: ExpressionConstantPool
Parameters:
pool (ExpressionConstantPool) : Expression constant pool.
Returns: This expression constant pool.
❱❱ Expression Evaluator
ExpressionEvaluator
Expression evaluator.
Fields:
isEvaluated (series bool) : `true` if the last evaluation completed successfully, `false` otherwise.
error (EvaluationError) : Error from the last evaluation attempt. If the last evaluation was successful, then this field is `na`.
result (series float) : Numeric result of the last evaluation.
boolResult (series bool) : Boolean result of the last evaluation.
createExpressionEvaluator()
Creates an expression evaluator.
Returns: Expression evaluator.
method evaluate(evaluator, expr)
Evaluates an expression.
Namespace types: ExpressionEvaluator
Parameters:
evaluator (ExpressionEvaluator) : Expression evaluator.
expr (Expression) : Expression to evaluate.
Returns: Numeric evaluation result.
For boolean-result expressions `1.0` means `true` and `0.0` means `false`.
Returns `na` if expression is empty.
method evaluateToBool(evaluator, expr)
Evaluates an expression.
Namespace types: ExpressionEvaluator
Parameters:
evaluator (ExpressionEvaluator) : Expression evaluator.
expr (Expression) : Expression to evaluate.
Returns: Boolean evaluation result.
Returns `false` if expression is empty.
method setFailOnDivisionByZero(evaluator, value)
Sets whether division or modulo by zero should fail evaluation.
Namespace types: ExpressionEvaluator
Parameters:
evaluator (ExpressionEvaluator) : Expression evaluator.
value (bool) : If `true`, division or modulo by zero fails evaluation. If `false`, it produces `na`.
Returns: This expression evaluator.
❱❱ Errors
ParseError
Error that occurred during expression parsing.
Fields:
message (series string) : Error message.
index (series int) : Character index where the parser detected the error.
EvaluationError
Error that occurred during expression evaluation.
Fields:
reason (series EvaluationErrorReason) : Error reason.
message (series string) : Error message.
Chart Patterns Screener [Trendoscope]🎲 Overview
Chart Patterns Screener is an advanced Pine Script designed to automatically detect and display classical chart patterns on PulseWire. It is a specialized, fine-tuned version of the popular Auto Chart Patterns indicator, optimized specifically for use with the Pine Screener.
🎲 How to Use This Script as Indicator
Since most of the settings are inherited from Auto Chart Patterns, let us first discuss on how to use the indicator before dwelving into the settings.
When you first add the indicator to a chart, you may see a large note or minimal visuals.
This is intentional.
Not to worry, This script is not designed to be used as a regular chart indicator. It was built primarily for scanning across many symbols using PulseWire’s Pine Screener utility.
Two Ways to Use It on Chart (if still needed):
Goto the settings of the script and disable the "Note" checkbox. This will let you use the script as an indicator - however the features as indicator are limited when compared to the original Auto Chart Patterns
Use the original Auto Chart Patterns
🎲 How to Use This Script with Pine-Screener
🎯 Add Script to Favourites
You must add this script to your favourites before it appears in the Pine Screener. You can do so by clicking on "Add to favourites" button in the top right corner of the script publication page.
🎯 Open Pine Screener Utility
Pine Screener can be opened directly through the direct link - www.pulsewire.com or we can go through the menus on pulsewire home page to find the utility.
🎯 Load Your watchlist
Watchlist can be created manually or through other screeners.
Here is a video publication on how to use Tradingivew default stock screener which also explains how to create a watchlist by using the utility.
For this demonstration, let us use one of the pre-existing watchlist. Also note that pine-screener has a limitation that watchlist can contain maxium 1000 symbols.
🎯 Load the script/Indicator to Pine-Screener
Click on "Choose Indicator" dropdown and select the Chart Pattern Screener from the alphabetically sorted list of indicators. Please note that the dropdown will only have the indicators that are marked as favourites.
Indicator settings and timeframe can be further changed to suit the needs.
🎯 Select Scanning Criteria and Scan
Indicator settings can have some options shown by default. However, users can click on the + symbol next to "Scan" button to explore other options Availlable.
Current Patterns - contain the number of any pattern available on specific instrument. Hence, they can be used to catch symbols having any pattern on the chart.
Other options however are to catch symbols having specific type of patterns
All the filter options have number of patterns of specific types actively present on each instruments. Hence, in order to filter the symbols having these types of patterns, we just need to set the condition if the value is more than 0
Examples
To find symbols with any active pattern:
Set condition: Current Patterns > 0
To find specific patterns (example):
Set condition: Rising Wedge > 0 to filter symbols having Rising Wedge patterns
Set condition: Ascending Channel > 0 to filter symbols having Ascending Channel patterns
Set condition: Contracting Triangle > 0 to filter symbols having Contracting Triangle patterns
Once you set the conditions, you can click on Scan to filter the watchlist
🎲 Settings Overview
🎯 Screener Settings
This group of settings contain parameters that are related to screener functionality.
A pattern formed 1000 bars ago may not really mean anything as all the trading opportunity of that pattern would be already over. Hence, in order to find active patterns we need to limit on how many bars back we should check. We provide two options to identify how far we need to look back
Fixed - In the fixed option, we rely on the Fixed number of bars to look back for active patterns. If Fixed bars are 20, this means, we only look for patterns that are formed in last 20 bars.
Relative - This is slightly complex option where number of bars to look before depends on the size of the pattern. We set the percentage of size as number of bars through input. This means, if the pattern spawns 100 bars, the screener will flag it only if it is formed within 20 bars (considering 20% is set as relative percent). However, bigger pattern spawing 150 bars will be flagged if it has formed within 30 bars.
🎯 Chart Pattern Settings
These settings are same as what is defined in Auto Chart Patterns .
🎲 Refernces
Pine Screener - Powerful tool for building programmable screener
Tradingview Stock Screener - Simple and powerful tool
Auto Chart Patterns .
Machine Learning RSI | AI Classification & Ranking (Zeiierman)█ Overview
The Machine Learning RSI | AI Classification & Ranking (Zeiierman) is an adaptive RSI intelligence system that combines momentum analysis, historical analog recognition, machine learning classification, confidence scoring, and dynamic trend management into a single framework.
Rather than interpreting RSI solely through traditional overbought and oversold thresholds, the indicator examines how similar RSI environments have behaved historically and uses those observations to classify current market conditions.
The script transforms RSI into a multi-dimensional feature space, stores historical market behavior, identifies the closest historical analogs, and allows those analogs to vote on future directional bias.
An adaptive feature-optimization engine then continuously learns which RSI characteristics provide the greatest predictive value under current market conditions.
The result is a hybrid system that blends:
• Multi-dimensional RSI analysis
• Historical analog matching
• Machine learning classification
• Adaptive feature weighting
• Rank & confidence scoring
• AI-driven trend management
█ Why is this one unique
This is not a normal RSI. It is a full analog classification engine built in Pine Script v6. It turns RSI behavior into an 8-feature market fingerprint, stores historical examples, labels them by future outcome, finds the closest past situations, lets those analogs vote, then converts the result into an adaptive ML RSI, rank/confidence scores, signals, and an ML-modulated Supertrend.
⚪ What it does
At a high level:
1. Builds 8 RSI-derived features
It does not only use the RSI value. It models:
RSI level, slope, acceleration, distance from 50, percentile rank, RSI volatility, fast/slow RSI spread, and smoothed RSI regime.
That means each bar becomes a multi-dimensional “state” of momentum, not just “RSI is 63.”
2. Creates a memory bank
Each confirmed bar is stored with its feature snapshot and a future outcome label. The label is based on whether price moved up or down after a fixed horizon, scaled by ATR. That is the learning dataset.
3. Uses K-nearest-neighbor analog matching
For the current bar, the script scans the historical bank and finds the closest past examples. It uses a Lorentzian-style compressed distance:
log(1 + abs(feature difference))
That is good because it reduces the impact of outliers. Huge feature mismatches do not completely dominate the model.
4. Lets analogs vote
Nearest neighbors vote bull or bear, weighted by distance. Closer matches matter more. The output becomes: analogScore, bias direction, agreement fraction, and gap tightness.
5. Auto-optimizes feature weights
This is one of the most sophisticated parts. The script uses a Fisher-discriminant-style calculation to determine which RSI features currently best separate bullish vs. bearish outcomes. Then it rescales those weights and smooths them over time.
So the model can learn that, for example, RSI slope matters more on one instrument, while RSI percentile or regime matters more on another.
6. Builds rank and confidence
Signals are not triggered just because the model flips bullish or bearish. They must pass a quality system:
Rank blends agreement, distance tightness, trend alignment, volatility health, regime fit, slope fit, smoothness, persistence, and penalties for chop or early flips.
Confidence focuses more on analog agreement, tightness, persistence, and slope fit.
This is much better than a simple buy/sell oscillator because it asks: “Is this setup actually supported?”
7. Adds adaptive Supertrend
The Supertrend is not static. Its band width changes based on ML conviction. High conviction tightens the trailing stop. Low conviction or chop widens it. That makes the trend system responsive without being blindly reactive.
⚪ Why it is good
The strongest part is that it combines machine learning logic, technical architecture, and trade-quality filtering into a single system.
Most PulseWire indicators are fixed formulas: RSI crosses 30, MACD crosses, Supertrend flips, moving average slope changes. This code differs because it creates a small local learning model directly in Pine.
The unique edge is the combination of:
• Feature engineering: RSI is transformed into 8 separate behavioral dimensions.
• Historical analog learning: Current market conditions are compared to past similar conditions.
• Distance-weighted voting: Closer historical examples have more influence.
• Auto feature weighting: The system adapts which features matter most.
• ATR-based outcome labeling: Learning is normalized by volatility, not just raw price movement.
• Quality scoring: Signals require both rank and confidence.
• Adaptive trend logic: The ML engine not only generates oscillator signals but also modifies Supertrend behavior.
That combination is rare in Pine Script. PulseWire supports advanced data structures such as arrays, matrices, and user-defined types, but many public scripts still use simpler procedural indicator logic. This script uses those advanced structures as a true modeling framework.
⚪ What makes it sophisticated
The code actually implements an AI-style classification workflow:
Input features → labeled memory → nearest-neighbor search → weighted classification → confidence scoring → adaptive output.
That is a real machine-learning pattern.
But this script goes further than a basic KNN signal tool because it adds:
• Auto-optimized feature weights using class separation.
• Rank/confidence gates instead of raw prediction signals.
• Chop, volatility, and trend filters to reduce bad market conditions.
• ML-driven Supertrend adaptivity rather than using ML only for arrows.
• Non-repainting signal discipline by firing on confirmed bars only.
⚪ Why It’s Marketable
Most RSI indicators treat every reading the same. This tool takes a different approach by analyzing how similar RSI conditions performed in the past and evaluating the current setup against those historical patterns. It only generates signals when multiple factors align, including confidence, trend direction, volatility, and market structure.
What makes it valuable is that it transforms RSI from a simple momentum oscillator into a context-aware decision framework. Rather than reacting to fixed overbought and oversold levels, it identifies recurring market behaviors, measures the similarity of current conditions to historical examples, and assigns a quality score to each opportunity. It then filters out low-probability environments and dynamically adjusts its trend management based on the strength of the model's conviction.
The result is a more selective, adaptive, and intelligent signal engine that helps traders focus on higher-quality setups instead of every RSI fluctuation. This moves well beyond the capabilities of a conventional PulseWire RSI indicator.
⚪ Main weakness
It is not deep learning, and it does not train a neural network. It is an online analog classifier. That is still legitimate AI-style logic. Also, because it learns from historical analogs inside the chart, performance depends heavily on market regime, symbol, timeframe, memory depth, and filters.
█ How It Works
⚪ Machine Learning Feature Engine
Most RSI indicators analyze a single value.
The Machine Learning RSI transforms RSI into a complete momentum fingerprint, consisting of eight independent characteristics that describe how momentum behaves beneath the surface.
The model analyzes:
• RSI Value
• RSI Slope
• RSI Acceleration
• Distance From Neutral (50)
• RSI Percentile Rank
• RSI Volatility
• Fast vs Slow RSI Spread
• RSI Regime Structure
Features cur = Features.new(
rOsc / 100.0,
scale01(rOsc - rOsc , winLen),
scale01(rOsc - rOsc -
(rOsc - rOsc ), winLen),
math.abs(rOsc - 50.0) / 50.0,
ta.percentrank(rOsc, winLen) / 100.0,
scale01(ta.stdev(rOsc, 14), winLen),
scale01(rOscF - rOscS, winLen),
scale01(ta.ema(rOsc, 20) - 50.0, winLen)
)
Together these features create a much richer representation of market behavior than traditional RSI calculations.
Instead of asking:
“Where is RSI?”
The model asks:
“What type of momentum behavior is currently occurring?”
⚪ Historical Analog Memory
The indicator continuously builds a memory bank of historical market behavior.
Every confirmed bar is stored together with its RSI fingerprint and the future outcome that followed.
row = array.from(
fVal, fSlp, fAcc, fMid,
fPct, fChn, fSpr, fReg,
float(outcome)
)
bank.add_row(0, row)
Over time the model accumulates hundreds or even thousands of historical observations.
Each observation becomes a real market example the system can reference later.
Rather than relying entirely on fixed formulas, the indicator learns from historical market behavior.
⚪ AI Classification Engine
Once the memory bank has been built, the Machine Learning RSI begins searching for historical situations that closely resemble the current market.
The comparison is performed across all eight RSI features simultaneously.
g = cur.gapTo(row, wts)
Similarity is measured using a weighted Lorentzian distance function.
compress(float d) =>
math.log(1.0 + math.abs(d))
Unlike traditional distance calculations, logarithmic compression reduces the influence of extreme outliers and prevents a single feature from dominating the comparison process.
This creates a more stable and robust analog matching system.
The objective is not to find identical charts.
The objective is to find historical momentum environments that behaved similarly.
⚪ Historical Analog Voting
After locating the closest historical matches, the system allows them to vote on the current market direction.
Closer analogs receive greater influence while weaker matches contribute less.
float w = 1.0 / (1.0 + n.gap)
v.score := v.score + n.cls * w
The weighted votes are combined into a final classification score.
eng.analogScore :=
vote.total > 0
? vote.score / vote.total
: 0.0
This process produces:
• Directional Bias
• Analog Agreement
• Classification Strength
• Similarity Quality
• Market Conviction
Rather than attempting to predict the future directly, the model asks:
“How did the most similar momentum environments behave when they occurred previously?”
⚪ Adaptive Feature Optimizer
Markets are constantly changing.
Features that are highly predictive in one environment may become less useful in another.
To solve this problem, the Machine Learning RSI includes an adaptive feature optimization engine.
The model continuously evaluates which RSI characteristics are doing the best job separating bullish outcomes from bearish outcomes.
float f =
math.pow(mB - mBe, 2)
/
(vB + vBe + 1e-6)
This process is based on Fisher Discriminant Analysis.
Features that consistently separate winning conditions from losing conditions receive larger weights.
Features that lose predictive power gradually receive less influence.
wts.value := wAuto.get(0)
wts.slope := wAuto.get(1)
wts.accel := wAuto.get(2)
wts.mid := wAuto.get(3)
This allows the model to adapt automatically to changing market conditions without requiring constant manual optimization.
⚪ Rank & Confidence Engine
Most indicators generate signals immediately after a condition is met.
The Machine Learning RSI goes several steps further. Every setup receives two independent evaluations.
• Rank → Measures setup quality.
• Confidence → Measures model conviction.
Rank evaluates:
• Historical agreement
• Analog quality
• Trend alignment
• Volatility conditions
• Regime structure
• Momentum consistency
• Market stability
Confidence evaluates:
• Historical consensus
• Analog clustering
• Directional consistency
• Signal persistence
• Structural confirmation
setup.rank := rankScore(…)
setup.conf := confScore(…)
Signals are only generated once both quality and confidence requirements have been satisfied.
This helps filter weaker market conditions while prioritizing stronger opportunities.
⚪ AI-Driven Learning System
The Machine Learning RSI does not simply memorize historical outcomes.
It learns what constitutes a meaningful outcome.
Each historical observation is classified based on future movement relative to current volatility.
outcome =
moveFwd > 2 * bandFwd ? 3 :
moveFwd > bandFwd ? 2 :
moveFwd > 0 ? 1 :
moveFwd < -2 * bandFwd ? -3 :
moveFwd < -bandFwd ? -2 :
moveFwd < 0 ? -1 : 0
• Large bullish moves receive stronger bullish labels.
• Large bearish moves receive stronger bearish labels.
• Small movements receive weaker classifications.
This allows the model to distinguish meaningful market behavior from ordinary noise.
⚪ ML Supertrend System
The indicator includes an adaptive Machine Learning Supertrend that responds to model conviction.
Unlike traditional Supertrends that rely on a fixed ATR multiplier, the ML Supertrend dynamically adjusts its sensitivity based on classification strength.
mlDrive =
math.abs(convSmoothed) * 0.5 +
eng.gapTight * 0.3 +
eng.agreeFrac * 0.2
As conviction increases:
• Bands tighten
• Trend changes become faster
• Stops become more responsive
As conviction decreases:
• Bands widen
• Noise tolerance increases
• Whipsaws are reduced
adaptMult =
stMultBase *
(1.0 + stMlResp * (1.0 - mlDrive))
This creates a trend-following system that adapts to the strength of the model’s conviction rather than relying solely on volatility.
█ How To Use
⚪ Reading The ML RSI
The Machine Learning RSI ranges from 0 to 100.
• Values above 50 suggest bullish momentum conditions dominate the market.
• Values below 50 suggest bearish momentum conditions dominate the market.
• Readings above 70 typically indicate strong bullish conditions, while readings below 30 suggest strong bearish pressure.
⚪ Reading The Signals
The Machine Learning RSI generates signals when the model detects a meaningful shift in market conditions and that shift passes both its quality and confidence requirements.
• Long signals indicate that the classification engine has identified a bullish market environment supported by historical analog agreement, trend structure, and market conditions.
• Short signals indicate that the classification engine has identified a bearish market environment supported by historical analog agreement, trend structure, and market conditions.
⚪ Using The ML Supertrend
The ML Supertrend acts as both a trend filter and a dynamic trailing stop.
• When the Supertrend flips bullish, the model considers the market to be operating in an uptrend regime.
• When the Supertrend flips bearish, the model considers the market to be operating in a downtrend regime.
█ Settings
Price Source: controls the price data used to build every RSI feature inside the learning engine.
Base RSI Length: controls the main RSI period used to create the ML RSI and its feature set.
Memory Depth: controls how many historical bars the model stores and searches when looking for similar market conditions.
Analog Count (k): controls how many closest historical matches are allowed to vote on the current market direction.
Show Signal Markers: toggles the Long and Short signal markers on the chart.
Candle Coloring: colors candles based on the current ML Supertrend regime.
Min Rank to Signal: controls the minimum setup-quality score required before a signal can appear.
Min Confidence to Signal: controls the minimum model conviction required before a signal can appear.
Trend Gate: requires signals to align with the ML Supertrend direction.
Volatility Band: filters signals so they only appear in healthier volatility conditions.
Min Vol Rank: controls the lower volatility threshold required for signals.
Chop Filter: blocks signals during choppy, range-bound market conditions.
Learning Sensitivity: controls how large a future move must be before the model treats it as a meaningful historical outcome.
Auto-Optimize Weights: allows the model to automatically learn which RSI features are most important.
Adaptation Speed: controls how quickly the learned feature weights adjust to changing market behavior.
Feature Weights: manually control the importance of each RSI feature when Auto-Optimize Weights is disabled.
Show ML Supertrend: toggles the adaptive ML Supertrend line, cloud, and trend visuals.
Supertrend Source: controls the price source used to build the ML Supertrend bands.
ATR Multiplier: controls the base distance of the ML Supertrend from price.
ML Band Adaptivity: controls how strongly model conviction adjusts the Supertrend band width.
RSI Signal Line Type: selects the moving average style displayed on the ML RSI.
RSI Signal Line Length: controls the smoothing length of the RSI signal line.
BB StdDev: controls the Bollinger Band width when using SMA + Bollinger Bands.
Colors: customize signal markers, candle coloring, ML RSI colors, Supertrend colors, cloud colors, and signal line visuals.
-----------------
Disclaimer
The content provided in my scripts, indicators, ideas, algorithms, and systems is for educational and informational purposes only. It does not constitute financial advice, investment recommendations, or a solicitation to buy or sell any financial instruments. I will not accept liability for any loss or damage, including without limitation any loss of profit, which may arise directly or indirectly from the use of or reliance on such information.
All investments involve risk, and the past performance of a security, industry, sector, market, financial product, trading strategy, backtest, or individual's trading does not guarantee future results or returns. Investors are fully responsible for any investment decisions they make. Such decisions should be based solely on an evaluation of their financial circumstances, investment objectives, risk tolerance, and liquidity needs.
Polynomial/Linear Regression Volume Profile [BigBeluga]Polynomial/Linear Regression Volume Profile is a state-of-the-art charting framework that blends advanced statistical modeling with localized volume distribution analysis. By evolving past traditional, static horizontal volume profiles, this indicator dynamically curves the volume profile matrix around mathematical trend baselines, giving you a hyper-localized view of value zones, support, and resistance across the trend’s lifecycle.
Equipped with a switchable Ordinary Least Squares (OLS) calculation engine, traders can analyze price distribution relative to a straight path (Linear) or an adaptive structural arc (Polynomial).
🔵 RECURSIVE REGRESSION BASELINES
Adaptive Curve Fitting Engine: Choose between a straight-line trend tracking framework (Linear) or an advanced second-degree curved path (Polynomial). This non-linear baseline curves dynamically to track real institutional momentum shifts, avoiding the lag or rigid delays typical of standard moving averages.
Symmetric Grid Segmentation: The indicator slices the regression space into dynamic parallel layers above and below the center line. These tracking cells act as a structural map of the trend, automatically expanding or contracting based on the mathematical bounds of the lookback period.
Standard Deviation Wave Bands: Plots dedicated tracking envelopes at 1, 2, and 3 Standard Deviations. This maps statistical extremes instantly, highlighting key valuation zones directly on the chart.
🔵 CURVED ORDER FLOW PROFILE
Dynamic Trend-Anchored Volume Profile: Traditional volume profiles are anchored strictly to vertical price grids. This framework bends the profile horizontally along the path of the regression curve. This ensures volume is localized directly relative to the trend's value matrix rather than arbitrary static prices.
Dynamic Point of Control Matrix (POC): The tool calculates cumulative transaction weights across each regression row. The absolute highest volume cluster is highlighted across the entire lookback window as a vivid Point of Control (POC) baseline, serving as a primary target magnet for price discovery.
Gradient Density Mapping: Volume bins are colored with a responsive heat-map gradient. Low-volume zones fade into deep baseline tones, while high-volume institutional interest areas light up dynamically, reflecting heavy positional accumulation.
🔵 DATA INTERFACE & CONTROLS
Regression Matrix Dashboard (Top-Right): A neat information center providing live metrics, including current trend direction (Bullish/Bearish), the numerical value of the POC level, the exact transactional volume resting at that key node, and structural $\pm3\text{ SD}$ channel limits.
Precision Profile Scaling: Adjust the profile width parameters to limit or extend how far back profile bins stretch across your chart space to prevent layout clutter.
Complete Style Personalization: Individualized visual controls allow you to switch line architectures (Solid, Dashed, Dotted) across baselines, boundaries, and POC paths.
🔵 STRATEGIC APPLICATION
Trading the Trend Value Nodes: Treat the dynamic POC line as a trend anchor. In a strong bullish trend, pullback entries occurring at a highly concentrated, heat-mapped POC node represent low-risk, high-probability entry criteria.
Mean Reversion at Statistical Boundaries: When price extends completely out to the dynamic outer channel limit and volume density in that outer bin thins out, look for a swift mean-reversion snapback toward the baseline.
Volume Profile Breakouts: Low-volume zones (gaps in the curved profile) indicate price levels that the market skipped quickly due to high momentum. If price breaks past a thick volume node into a low-volume zone, it is likely to sprint quickly toward the next major heat-mapped node.
Structural Regime Tracking: Use the upper-right dashboard to instantly evaluate macro status. If the matrix shifts between Bullish and Bearish while price hovers consistently near a high-volume POC, it implies heavy institutional distribution is occurring before the next major expansion.
Polynomial/Linear Regression Volume Profile redefines volume structure. By wrapping the laws of order flow directly around mathematical curves, it gives trend traders an elite perspective to trade with precision, statistical logic, and institutional order flow visibility.
Whale Liquidity and Absorption Profile [AlgoAlpha]🟠 OVERVIEW
The Whale Liquidity and Absorption Profile maps intrabar buying, selling, delta, and absorption activity into stacked horizontal profiles. It samples lower timeframe volume data inside each chart candle, then groups that activity into price bins to show where aggressive participation and absorption occurred across a configurable lookback range.
The script separates strong and weak activity using a percentile-based strength filter. It also builds a delta heatmap, absorption profile, historical absorption heatmap, and local absorption zones. Together, these components help traders identify where liquidity entered the market, where imbalance formed, and where price may react again.
🟠 CONCEPTS
Intrabar Sampling — Lower timeframe volume and directional data are requested using request.security_lower_tf() to reconstruct buying and selling activity inside each chart candle.
Strength Filter — Intrabar volume samples are ranked by percentile. Volumes above the selected percentile threshold are classified as strong activity while lower values are treated as weak activity.
Delta Profile — Buy volume minus sell volume calculated per price bin. Positive delta shows aggressive buying while negative delta shows aggressive selling.
Absorption Volume — Bullish volume occurring in upper wicks and bearish volume occurring in lower wicks. This is used to estimate where opposing liquidity absorbed incoming pressure.
Price Bins — The full price range inside the lookback is divided into vertical bins. All volume, delta, and absorption calculations are aggregated into these bins.
Absorption Peaks — Local highs in the absorption profile compared against neighboring bins. These areas are drawn as support and resistance zones.
🟠 FEATURES
Multi-Layer Volume Profile — Displays stacked buying and selling activity across price levels.
• Separates strong bullish, weak bullish, weak bearish, and strong bearish volume.
• Optional strong-only mode hides weak participation and normalizes the profile using only strong activity.
Delta Heatmap — Displays signed delta values directly inside each profile cell.
• Positive delta highlights dominant buying pressure.
• Negative delta highlights dominant selling pressure.
Absorption Profile — Aggregates wick-based absorption activity into a separate horizontal profile. (Buys at high wicks, Sells at low wicks)
Historical Absorption Heatmap — Creates rolling 5-bar heatmap snapshots to show where historical absorption accumulated over time.
Absorption Zones — Detects local absorption peaks and projects them across the chart as potential reaction areas.
Strong Activity Bubbles — Marks the strongest intrabar buying and selling events directly on price using percentile-ranked bubble tiers.
🟠 HOW TO USE
Load 2 instances of the indicator to bypass box drawing limits and use both the Absorption heatmap and the profiles.
Watch for stacked strong bullish volume combined with positive delta — this can show aggressive participation entering a price region.
Watch for stacked strong bearish volume combined with negative delta — this can show heavy selling pressure dominating a level.
Use absorption zones as areas where price previously encountered opposing liquidity — these zones may act as future reaction points.
Compare delta against absorption — strong positive delta with heavy upper-wick absorption can indicate trapped buyers or resistance.
Use the historical absorption heatmap to locate repeated liquidity interaction zones that price continues to respect over time.
Increase profile resolution for tighter price detail and reduce it for broader structural zones.
Enable strong-only mode to isolate high-participation liquidity events and remove weaker intrabar activity from the profile.
🟠 CONCLUSION
Whale Liquidity and Absorption Profile combines intrabar volume profiling, delta analysis, and wick-based absorption detection into a single structured framework. The indicator separates strong and weak participation while mapping where liquidity was absorbed across price levels. This gives traders a clearer view of imbalance, participation strength, and potential reaction zones inside the current market structure.
Fractional EMA Kalman Filter [D7]Fractional EMA Kalman Filter
1. Description
Fractional EMA Kalman Filter is an experimental smoothing and state-estimation tool that combines a Kalman filter framework with a fractional EMA input. The objective is to create a filter that remains subdued during ranging conditions while retaining responsiveness when directional structure emerges. By integrating adaptive variance logic and residual-based state control, D7 aims to balance smoothness, reactivity, and structural awareness.
2. Construct
D7 uses a single-state Kalman architecture with the following components:
State (X)
The current filtered estimate of price, updated using a fractional-length EMA measurement input. This is what the filter believes the true underlying value to be at each step.
Innovation
The difference between the incoming measurement and the current state estimate; the correction signal the filter acts on. Innovation = EMA − X
Kalman Gain (K)
The adaptive weighting factor that balances trust between the new measurement and the prior prediction. K = P / (P + R)
P - State uncertainty variance
Represents how uncertain the filter is about its current state estimate.
Q - Process noise variance
Represents uncertainty in how the state evolves between steps. Higher Q makes the filter more responsive to change.
R - Measurement noise variance
Represents uncertainty in the incoming measurement. Higher R makes the filter more sceptical of new observations. State Update
X := X + K × Innovation
P := (1 − K) × (P + Q)
3. Features
Fractional EMA Layer
Applies a double sub‑integer EMA length (e.g., 0.5‑period) to generate a high‑resolution measurement signal that responds to structural volatility and regime transitions faster than conventional smoothing. The fractional EMA is not intended to estimate price, but to precondition the observation space so that regime changes manifest as large, legible innovations. By transforming the input prior to filtering, the measurement emphasizes moments of structural change rather than steady‑state behavior, improving the responsiveness of adaptive state estimation.
Innovation Engine
Computes the innovation/residual error squared and normalizes by ATR, producing a scale-consistent noise estimate that adapts across instruments without manual recalibration.
Dynamic Q/R Model
Process noise (Q) and measurement noise (R) are derived from short and long lookback windows of the normalized residual variance. This allows the filter to adapt using its own tracking error rather than relying solely on external volatility measures. When prediction error is consistently large, the filter knows its model is inadequate for current conditions and adjusts accordingly.
Most Kalman filter implementations use either fixed Q or R values or; external volatility proxies such as ATR to modulate Q/R. These approaches respond to price volatility, but not to whether the filter itself is tracking effectively.
R Gate
A sensitivity floor applied to the residual variance. This ensures R does not fall below a structurally meaningful minimum, preventing the filter from becoming overconfident during low-volatility periods.
4. Rationale
Fractional EMA
A fractional EMA intentionally overshoots and oscillates around price, producing a bounded zig‑zag structure. This behavior is intentional by design: the oscillation self‑balances under steady conditions, while disruptions to that balance generate exaggerated transient values during regime or volatility shifts. Compared to raw price or conventionally smoothed inputs, the fractional EMA yields an information‑dense measurement stream in which structural transitions are amplified, improving innovation visibility for adaptive Kalman filtering.
Adaptive Q/R
Rather than relying on fixed noise parameters, D7 continuously derives Q and R from residual behavior. This allows the filter to self-tune across changing market conditions without manual recalibration:
- During directional persistence, responsiveness increases
- During noisy or unstable conditions, caution or scepticism increases
- Prediction error becomes part of the control loop rather than external tuning
A core design goal of D7 is structural suppression during sideways conditions. When price lacks directional intent, residual variance decreases, causing Q to fall relative to R. This reduces the Kalman gain, and the filter trusts its prior state estimate more than incoming measurements. The result is a filter that compresses and flattens naturally, rather than oscillating as conventional moving averages do through range-bound price action.
5. Display Overview
The chart displays both a baseline Kalman filter and a faster-reacting Kalman filter simultaneously, allowing users to visually compare responsiveness, smoothness, and structural behavior under different parameter settings. This dual-filter layout is intended to help users observe how changes in Q/R dynamics and filter sensitivity affect state estimation across varying market conditions.
The fractional EMA input can also be optionally displayed. This observation is turned off by default to preserve chart clarity but may be enabled for users who wish to examine the relationship between the conditioned input signal and the resulting Kalman state estimates.
6. Default Settings
EMA lengths: 0.5, 0.5
Kalman base and fast filter settings:
Q multiplier (process noise): 0.002, 0.005
R (measurement noise): 50, 50
R Gate: 1, 1
7. Future Development
D7 serves as the foundational implementation of the Kalman filter within the ET Massif framework. Subsequent versions will introduce external regime-aware Q/R modulation and multi-state architecture, progressively increasing adaptability while preserving the core filter mechanics established here.
日本語概要 (Japanese Summary)
Fractional EMA Kalman Filter は、カルマンフィルターに Fractional EMA(小数期間EMA) を組み合わせた研究用インジケーターです。Fractional EMA とは、期間設定に整数ではなく小数点以下の値を用いる手法です。これにより、従来のEMAよりも短期的な価格変化やボラティリティの拡大を、より高精度かつ敏感に捉えることが可能になります。本インジケーターは、レンジ相場ではラインが水平(フラット)に推移してノイズを軽減する一方、トレンド発生時には極めて素早く反応するように設計されています。
中文概要(Chinese Summary)
Fractional EMA Kalman Filter 是一款結合卡爾曼濾波(Kalman Filter)與 分數階 EMA(Fractional EMA) 的研究型技術指標。所謂的 Fractional EMA,是指打破傳統整數週期的限制,採用小數參數進行計算。這項技術能更細膩地捕捉短期價格波動與結構性變化,提供比傳統指標更高的靈敏度。本指標的核心設計在於:橫盤整理時維持平滑走勢以過濾雜訊,而在趨勢發動時展現極速反應。此外,它能透過殘差(Residuals)動態調整參數 Q(過程雜訊)與 R(測量雜訊),確保在不同波動率下的自我適應能力。
Disclaimer:
This script is a research tool for market structure analysis and educational purposes only. It does not constitute financial advice. Trading involves risk.
Neural Weight Oscillator (Zeiierman)█ Overview
The Neural Weight Oscillator (Zeiierman) is an adaptive multi-factor oscillator that combines structured decision-making with dynamic market learning.
The script analyzes three core market behaviors: Trend, Mean Reversion, and Momentum. Instead of treating these components equally, the oscillator uses the Best-Worst Method (BWM) to determine which market behavior should have the greatest influence under current market conditions.
An adaptive training layer then studies historical market reactions and gradually amplifies the features that have recently produced the strongest directional behavior.
The result is a hybrid oscillator that blends:
Human-defined market logic
Adaptive feature weighting
Multi-factor momentum analysis
Dynamic market learning
Unlike traditional oscillators that rely on static formulas, the Neural Weight Oscillator continuously adjusts its internal structure based on both trader-defined weighting preferences and changing market behavior.
█ How It Works
⚪ Market Structure Engine
The oscillator builds its analysis from three independent behavioral models: Trend, Mean Reversion, and Momentum.
The Trend component measures structural direction by comparing the fast EMA against the slow EMA, then adds the EMA slope to capture acceleration.
trendSpread = (emaFast - emaSlow) / atr
trendSlope = (emaFast - emaFast ) / atr
trendScore = normalize(trendSpread + trendSlope, -2.5, 2.5)
The Mean Reversion component measures stretched conditions using RSI exhaustion and statistical deviation from the market mean.
zScore = dev == 0 ? 0 : (close - basis) / dev
meanScore = (100 - rsi) * 0.5 + normalize(-zScore, -2.5, 2.5) * 0.5
The Momentum component measures directional acceleration using ROC, RSI momentum, and EMA velocity.
rocNorm = normalize(close / close - 1.0, -0.05, 0.05)
momentumScore = rocNorm * 0.45 + rsi * 0.35 + emaMomentum * 0.20
Each component produces its own normalized score before being blended into the final oscillator.
⚪ Best-Worst Method (BWM)
The core weighting system in the oscillator is based on the Best-Worst Method (BWM), a structured decision-making framework that creates balanced weighting relationships among multiple factors.
bestIdx = criterionIndex(bestCriterion)
worstIdx = criterionIndex(worstCriterion)
array.set(bo, bestIdx, 1.0)
array.set(ow, worstIdx, 1.0)
Instead of assigning arbitrary percentages manually, BWM allows the trader to define which market behavior matters most and which matters least. The script then automatically calculates balanced internal weights.
The process begins by selecting:
The “Best” factor → the market behavior trusted most
The “Worst” factor → the market behavior trusted least
relWeight = math.sqrt((aBW / boVal) * owVal)
The oscillator then compares all remaining factors relative to those two extremes and converts those relationships into normalized internal weights.
⚪ How To Think About The BWM Weights
The easiest way to think about BWM is:
“What type of market behavior do I trust most in the current environment?”
Different market conditions naturally favor different behaviors.
In strong directional trends , traders often prioritize Trend because structural continuation becomes the dominant force.
In choppy or range-bound markets , Mean Reversion may become more important because the market repeatedly returns back toward equilibrium.
During aggressive breakout environments , Momentum may deserve the highest weighting because acceleration becomes the primary driver.
The goal is not to find a “perfect” weight configuration, but rather to align the oscillator with the type of behavior currently dominating the market.
⚪ Adaptive Neural Training Layer
The oscillator includes an adaptive learning layer that learns how the market has recently reacted to the model’s internal features.
The script looks back at prior Trend, Mean Reversion, and Momentum feature values, then compares them to the future price reaction.
target = close / close - 1.0
targetDirection = target > 0 ? 1.0 : target < 0 ? -1.0 : 0.0
High-quality samples are ranked by how strong the move was relative to volatility.
sampleScore = math.abs(target) / qualityVol
The model then compares its internal prediction against the actual market direction and adjusts the learned feature weights over time.
pred = twTrend * s.trend + twMean * s.mean + twMomentum * s.momentum + tbias
err = pred - s.target
This allows the oscillator to gradually learn which features are producing the strongest directional behavior.
⚪ Adaptive Feature Amplification
The learned weights are converted into feature amplifiers.
trendAmplifier = 1.0 + learnTrend * blend
meanAmplifier = 1.0 + learnMean * blend
momentumAmplifier = 1.0 + learnMomentum * blend
This allows stronger features to gain more influence, while weaker features receive less influence.
█ How to Use
⚪ Reading the Oscillator
The oscillator operates between 0 and 100.
Values above 50 suggest bullish pressure dominates the market, while values below 50 suggest bearish pressure dominates.
As the oscillator moves farther away from the neutral 50 level, directional imbalance becomes stronger.
Readings above 70 typically indicate strong bullish expansion, while readings below 30 indicate strong bearish pressure. Extreme zones above 80 or below 20 may signal exhaustion conditions where reversals become more likely.
⚪ Using the BWM Weighting System
The BWM system allows traders to align the oscillator with current market behavior by controlling how much influence Trend, Mean Reversion, and Momentum should have inside the model.
Imagine the market is trending strongly upward.
You may believe:
Trend is the dominant market behavior.
Mean Reversion still matters during pullbacks.
Momentum should have the least influence.
In this case, you could choose:
Best = Trend
Worst = Momentum
You then control how strongly Trend dominates the other factors through the comparison inputs.
For example:
Best-to-Others:
Trend = 1
Mean = 3
Mom = 6
Relative-to-Worst:
Trend = 4
Mean = 2
Mom = 1
This tells the oscillator:
Trend is selected as the strongest market behavior.
Momentum is selected as the weakest market behavior.
Trend is 3x more important than Mean Reversion.
Trend is 6x more important than Momentum.
Mean Reversion is 2x more important than Momentum.
The script automatically converts these relationships into balanced internal weights.
As a result, the oscillator becomes more trend-sensitive while reducing the influence of short-term momentum fluctuations and weak counter-trend behavior.
If the market becomes highly rotational or range-bound, traders may instead increase the importance of Mean Reversion so the oscillator becomes more responsive to exhaustion and reversal conditions.
During aggressive breakout environments, increasing Momentum weighting can help the oscillator react faster to acceleration phases.
The weighting system is designed to adapt the oscillator’s personality to different market environments rather than forcing one static interpretation onto every condition.
█ Settings
Fast EMA: controls the responsiveness of the Trend and Momentum calculations.
Slow EMA: controls the structural trend baseline used throughout the oscillator.
Smoothing: controls the smoothness of the final oscillator line.
The Best and Worst: determine how the BWM weighting model prioritizes market behaviors.
Best-to-Others: define how strongly the selected Best factor dominates the remaining components.
Relative-to-Worst: define how much stronger each component is compared to the selected Worst factor.
Use Training: enables the adaptive learning layer.
Influence: controls how strongly the learned model amplifies features.
Line Impact: controls how much the adaptive model can directly influence the oscillator line itself.
-----------------
Disclaimer
The content provided in my scripts, indicators, ideas, algorithms, and systems is for educational and informational purposes only. It does not constitute financial advice, investment recommendations, or a solicitation to buy or sell any financial instruments. I will not accept liability for any loss or damage, including without limitation any loss of profit, which may arise directly or indirectly from the use of or reliance on such information.
All investments involve risk, and the past performance of a security, industry, sector, market, financial product, trading strategy, backtest, or individual's trading does not guarantee future results or returns. Investors are fully responsible for any investment decisions they make. Such decisions should be based solely on an evaluation of their financial circumstances, investment objectives, risk tolerance, and liquidity needs.
NeuraLib: A Native AI and Deep Learning RuntimeNeuraLib is a tensor-based, auto-differentiating Machine Learning runtime built natively for Pine Script™.
It brings real Deep Learning mechanisms that power modern Artificial Intelligence systems into PulseWire. Instead of relying on fixed formulas, static regressions, or rigid structures, NeuraLib gives Pine developers a different tool: a compact neural runtime that can learn from the features you feed it, using the architecture you define.
This means users are no longer limited to classical methods like Linear Regression, Logistic Regression, KNN, Naive Bayes, Kalman Filters, or Markov Chains. One can build adaptive architectures perfectly suited for custom indicators, strategies, regime detection, directional prediction, price transforms, and AI-assisted signal generation.
Using NeuraLib, one can build a model, collect market data, normalize it, run predictions, train through backpropagation, track validation behavior, and update weights directly inside PulseWire.
Furthermore, it is not necessary to directly display trained variables. The process can be a part of a larger script functionality, where AI-powered decision making changes how an indicator behaves.
The goal is to make real neural network workflows usable in Pine Script without hiding the important controls, being scalable with evolving market dynamics, and abstracting away the complexity that comes with such software. The provided API is highly modular and intuitive, using chained object-oriented programming for easy readability and use. The backend is engineered with fault-tolerance in mind, providing users with sanity checks and preventing common pitfalls by default.
Think of NeuraLib as a comprehensive machine learning ecosystem, containing:
A Model Builder : Define neural networks with readable chained calls like `.input()`, `.dense()`, and `.dropout()`.
An In-Pine Training Engine : Models calculate losses, backpropagate gradients, update weights, and produce predictions directly on chart data.
Automated Data Pipelines : Built-in datasets handle feature collection, robust scaling (Z-Score, Min-Max), validation holdout splits, and time-series rolling windows.
Finance-Native Loss Functions : Beyond standard error metrics, the engine includes Directional, Quantile, Multi-Horizon Weighted, and Sharpe-style losses tailored for trading.
Practical Training Controls : Layer Normalization, AdamW weight decay, gradient clipping, gradient accumulation, and early stopping are built in to prevent overfitting.
Advanced Optimizers : Train networks using RMSProp, Adam, or AdamW, paired with learning rate schedules like Warmup Cosine and Step Decay.
For newer users, this means you can start with a simple dense model. For advanced users, the same runtime exposes graph operations, custom blocks, tensors, matrix operations, optimizers, schedules, losses, and extension hooks.
In plain terms, a model receives a row of numbers called features, compares its output against a target, measures the error with a loss function, and then adjusts its internal weights to reduce that error next time.
----------------------------------------------------------------------------------------------------------------
🔷 WHAT MAKES IT DIFFERENT
🔸 Parity-tested neural math
NeuraLib’s core operations have been tested against established Machine Learning Runtimes outside of PulseWire (Such as Keras / TensorFlow / PyTorch).
The goal was not to imitate the appearance of Machine Learning, but to reproduce the math that is proven to work. Standard forward passes, gradients, losses, and optimizer behavior were checked for 1:1 algorithmic parity, with negligible differences coming from normal floating-point behavior.
That means the matrix math, backpropagation, and gradient updates running on your chart follow the same underlying logic expected from professional Machine Learning environments.
🔸 Matrix-first computation
NeuraLib uses tensor and matrix abstractions as the foundation of the runtime. Under the hood, it supports the operations needed for neural computation, including matrix multiplication, broadcasting, activation functions, softmax, slicing, concatenation, reductions, normalization, attention scoring, convolution-style operations, and recurrent scan blocks.
🔸 Auto-differentiating graph engine
NeuraLib makes the computational graph a first-class object.
You can use high-level Sequential models, or build custom GraphBlocks from lower-level operations. Once a custom block is connected to a model, the same runtime handles the backward pass. That means your custom architecture can be trained with the same `.trainOnBatch()` workflow as standard layers.
----------------------------------------------------------------------------------------------------------------
🔷 CUSTOM GRAPHS
The Sequential API is the easiest way to start, but NeuraLib is not just a list of built-in layers.
You can create a `GraphBlock`, add operations, set an output node, and plug that block into a model. Once connected, the runtime handles the backward pass and parameter updates.
Useful graph operations include:
Matrix multiplication, transpose, add, subtract, multiply, divide, and scale.
Activation functions and softmax.
Layer Normalization and Dropout.
Causal masking, slicing, concatenation, row reduction, and column reduction.
Global average pooling and global max pooling for 1D sequences.
Attention score and attention apply operations.
Conv1D, LSTM scan, and GRU scan primitives.
This is the foundation that allows companion model libraries to add advanced AI and Machine Learning architectures without changing the main NeuraLib runtime.
----------------------------------------------------------------------------------------------------------------
🔷 BUILT-IN DATA GUARDRAILS
NeuraLib is not only a training mechanism. It also includes guardrails for cleaner research:
Invalid rows are rejected : Dataset rows must match the configured feature and target counts, and rows containing `na` values are not inserted.
Shape checks protect model calls : Forward, training, backward, and evaluation paths validate input and target shapes before running expensive graph code.
Train and validation splits are separated : `trainBatch()` and `validationBatch()` use holdout rows instead of blending all rows into one batch.
Scaler leakage is controlled : Validation batches are scaled from the training-side profile where the dataset split requires it, so validation normalization does not learn from the holdout slice.
Rolling windows respect time order : `RollingDataset` supports target offsets and wrapped ring buffers while preserving chronological reads.
These checks help reduce common data poisoning and data leakage mistakes: wrong row widths, missing values, validation contamination, target-offset leakage, and accidental overtraining across every historical bar.
----------------------------------------------------------------------------------------------------------------
🔷 A FIRST MODEL
The basic API is intentionally readable. This creates a small model with dropout, one hidden layer, Huber loss, AdamW optimization, and MAE tracking.
//@version=6
indicator("NeuraLib Basic Model", overlay = false, calc_bars_count = 600)
import Alien_Algorithms/NeuraLib/1 as nl
var nl.Sequential model = nl.sequential("basic_model")
var float modelOutput = na
if barstate.isfirst
nl.CompileConfig cfg = nl.compileConfig()
cfg := cfg
.optimizer(nl.adamW(0.001))
.loss(nl.LossKind.huber)
.metric(nl.MetricKind.mae)
.withTrainingGate(true)
model := model
.input(array.from(4), "features")
.dropout(0.15)
.dense(8, nl.ActivationKind.relu, "hidden")
.dense(1, nl.ActivationKind.linear, "output")
.compile(cfg)
float rsiValue = ta.rsi(close, 14)
float emaValue = ta.ema(close, 21)
float atrValue = ta.atr(14)
float atrPct = close == 0.0 ? 0.0 : atrValue / close
float momentum = na(close ) or close == 0.0 ? 0.0 : close / close - 1.0
bool ready = not na(rsiValue) and not na(emaValue) and not na(atrPct) and not na(momentum)
if ready
float priceVsEma = emaValue == 0.0 ? 0.0 : close / emaValue - 1.0
nl.Tensor inputTensor = nl.vector(array.from(rsiValue, priceVsEma, atrPct, momentum), "features")
nl.Tensor outputTensor = model.predict(inputTensor)
modelOutput := outputTensor.get1d(0)
plot(modelOutput, "Untrained model output", color = color.aqua, linewidth = 2)
hline(0.0, "Zero", color = color.new(color.gray, 70))
The same model can then receive scaled batches from a dataset and train with `.trainOnBatch()`. The plot in this first example is the untrained forward output, included so the block can be pasted directly into an indicator.
----------------------------------------------------------------------------------------------------------------
🔷 A PRACTICAL DATA FLOW
Machine Learning models usually fail when the data pipeline is careless. Price, volume, volatility, and oscillators often live on very different scales. NeuraLib includes dataset and scaling helpers so the common workflow stays explicit:
Build a feature row.
Build a target row.
Push the row into a dataset.
Request a training batch.
Request a validation batch when needed.
Train, evaluate, predict, and inverse-scale targets when appropriate.
//@version=6
indicator("NeuraLib Return Validation Example", overlay = false, calc_bars_count = 600)
import Alien_Algorithms/NeuraLib/1 as nl
var nl.Sequential model = nl.sequential("returns_model")
var nl.WindowDataset dataset = nl.windowDataset(4, 1, 500, "returns_dataset")
var float predictedReturn = na
var float validationLossValue = na
var float trainingLossValue = na
if barstate.isfirst
nl.CompileConfig cfg = nl.compileConfig()
cfg := cfg
.optimizer(nl.adamW(0.003))
.loss(nl.LossKind.huber)
.metric(nl.MetricKind.mae)
.trainEveryCall()
model := model
.input(array.from(4), "features")
.dense(8, nl.ActivationKind.relu, "hidden")
.dropout(0.10, "dropout")
.dense(1, nl.ActivationKind.linear, "next_return")
.compile(cfg)
dataset := dataset
.setInputScaler(nl.ScalerKind.zScore)
.setTargetScaler(nl.ScalerKind.zScore)
float rsiValue = ta.rsi(close, 14)
float emaValue = ta.ema(close, 21)
float atrValue = ta.atr(14)
float atrPct = close == 0.0 ? 0.0 : atrValue / close
float momentum = na(close ) or close == 0.0 ? 0.0 : close / close - 1.0
float realizedReturn = na(close ) ? na : nl.nextReturnValue(close , close)
bool rowReady = not na(rsiValue ) and not na(emaValue ) and not na(atrPct ) and not na(momentum ) and not na(close )
if rowReady
float prevEma = emaValue
float priceVsEma = prevEma == 0.0 ? 0.0 : close / prevEma - 1.0
array features = array.from(
rsiValue ,
priceVsEma,
atrPct ,
momentum )
array target = array.from(nl.nextReturnValue(close , close))
dataset := dataset.pushRow(features, target)
if dataset.ready(64)
nl.Batch train = dataset.trainBatch(16)
nl.Batch validation = dataset.validationBatch(16)
model := model.trainOnBatch(train.inputTensor, train.targetTensor)
trainingLossValue := model.trainStats.lastLoss
nl.LossResult validationLoss = model.evaluate(validation.inputTensor, validation.targetTensor)
validationLossValue := validationLoss.value
bool liveReady = not na(rsiValue) and not na(emaValue) and not na(atrPct) and not na(momentum)
if liveReady
float livePriceVsEma = emaValue == 0.0 ? 0.0 : close / emaValue - 1.0
array liveFeatures = array.from(rsiValue, livePriceVsEma, atrPct, momentum)
nl.Tensor liveInput = nl.vector(liveFeatures, "live_features")
nl.Tensor scaledInput = dataset.scaleInput(liveInput)
nl.Tensor scaledPrediction = model.predict(scaledInput)
nl.Tensor rawPrediction = dataset.inverseScaleTarget(scaledPrediction)
predictedReturn := rawPrediction.get1d(0)
plot(realizedReturn, "Last realized return", color = color.gray)
plot(predictedReturn, "Predicted next return", color = color.aqua, linewidth = 2)
plot(validationLossValue, "Validation loss", color = color.orange)
plot(trainingLossValue, "Training loss", color = color.new(color.blue, 35))
hline(0.0, "Zero", color = color.new(color.gray, 70))
This example trains from completed historical pairs. The feature row comes from the previous bar, and the target is the return from that previous bar to the current bar. That keeps the example easy to inspect and avoids using future information in the feature row. When pasted into an indicator, it plots the last realized return, the model's predicted next return, training loss, and validation loss.
----------------------------------------------------------------------------------------------------------------
🔷 TWO PRACTICAL EXECUTION MODES
Deep Learning in Pine requires careful execution control. NeuraLib supports two main workflows.
🔸 1. Live-edge training
Use this when you want safer execution for larger models.
The dataset can collect rows across the chart, while the expensive training step only runs on the last confirmed historical bar. This helps avoid timeouts while still allowing the model to learn from recent prepared data.
cfg := cfg.withTrainingGate(true)
Use this for:
Larger models
More features
Rolling sequence inputs
Heavier architectures
Safer live-edge updates
🔸 2. Full-history training and inference
Use this when the model is intentionally small.
The model can train and infer across historical bars, which makes it possible to create lightweight adaptive indicators, such as an AI Moving Average that learns from recent local structure instead of using a fixed smoothing formula.
cfg := cfg.trainEveryCall()
Use this for:
Tiny dense models
Small batches
Fast adaptive filters
AI-assisted moving averages
Lightweight feature transforms
For full-history workflows, start small. A shallow model with 4 to 8 hidden units and a batch size of 8 or 16 is usually a better starting point than a deep architecture.
----------------------------------------------------------------------------------------------------------------
🔷 ADVANCED MODEL EXPANSION
NeuraLib is designed to act as the foundation for larger model libraries and community-built extensions.
To demonstrate this, NeuraLib Expansion: Advanced Model Layers is built entirely on top of the public NeuraLib API and is launched in parallel on day one. The expansion library is published as NeuraLib_Models . It extends the runtime with higher-level builders for LSTMs, GRUs, temporal convolution stacks, residual dense blocks, dueling Q-heads for Reinforcement Learning, Transformer-style attention blocks, and Prioritized Experience Replay utilities.
The important part is architectural: advanced models plug into the same runtime. NeuraLib remains the foundation for tensors, graph execution, optimization, training, inference, datasets, and scaling. After importing `NeuraLib_Models`, its fluent methods become available on NeuraLib `Sequential` models, so the expansion alias does not need to be referenced directly in the layer chain.
//@version=6
indicator("NeuraLib Models Extension Demo", overlay = false, calc_bars_count = 600)
import Alien_Algorithms/NeuraLib/1 as nl
import Alien_Algorithms/NeuraLib_Models/1 as models
var nl.Sequential model = nl.sequential("advanced_demo")
if barstate.isfirst
model := model
.input(array.from(8), "sequence")
.temporalConvStack(4, 2, 3, 2, 2, 1, nl.ActivationKind.relu, 0.0, "temporal")
.globalAvgPool1d(2, 3, "pool")
.duelingQHead(4, 2, nl.ActivationKind.relu, "q_head")
.build(nl.rng(7))
----------------------------------------------------------------------------------------------------------------
🔷 FEATURE QUICK REFERENCE
Runtime : Matrix-first auto-differentiating neural graph runtime for Pine Script.
Model API : Chainable `Sequential` builder with `input`, `dense`, `dropout`, `layerNorm`, `activation`, `flatten`, `reshape`, and custom `block` support.
Training : Forward pass, loss calculation, backpropagation, gradient accumulation, optimizer steps, train stats, and history buffers.
Inference : `.predict()` for deterministic inference and `.predictMC()` for dropout-based uncertainty sampling.
Datasets : `WindowDataset` for flat rows and `RollingDataset` for time-series windows.
Scaling : None, Z-Score, Min-Max, Running Z-Score scalers, dataset input scaling, target scaling, and inverse target scaling.
Optimizers : SGD, Momentum, RMSProp, Adam, and AdamW.
Schedulers : Constant, Step Decay, Cosine Decay, and Warmup Cosine.
Activations : Linear, ReLU, Leaky ReLU, ELU, GELU Approx, Sigmoid, Tanh, Softplus, Swish, and Softmax.
Losses : MSE, MAE, Huber, LogCosh, Binary Cross Entropy, Binary Cross Entropy From Logits, Categorical Cross Entropy, Softmax Cross Entropy From Logits, Directional, Quantile, Multi-Horizon Weighted, and Sharpe.
Metrics : MAE, RMSE, Directional Accuracy, Binary Accuracy, Binary Accuracy From Logits, Categorical Accuracy, and Cosine Similarity.
Guardrails : Shape validation, invalid-row rejection, train/validation split helpers, leakage-aware scaler profiles, training gates, gradient clipping, and EarlyStopper.
Advanced expansion : Conv1D, temporal stacks, recurrent blocks, attention, Transformers, dueling Q-heads, positional encodings, and Prioritized Experience Replay.
----------------------------------------------------------------------------------------------------------------
🔷 IMPORTANT CONSIDERATIONS
Start small : Pine Script is not a GPU training environment. Compact models are the right starting point.
Control chart history : Use `calc_bars_count = 600` in `indicator()` when needed to balance available training history against model size and execution time.
Use the training gate : For heavier models, use `.withTrainingGate(true)` so backpropagation runs only at the confirmed historical edge.
Scale your inputs : Raw market features often differ by orders of magnitude. Use dataset scalers unless you have a deliberate reason not to.
Validate separately : Use `trainBatch()` and `validationBatch()` to monitor generalization instead of only watching training loss.
Avoid lookahead : Build feature rows only from information available at the time of the row. Use completed target rows for training.
Treat outputs as research signals : NeuraLib provides model mechanics. Strategy design, risk management, and market assumptions remain the user's responsibility.
----------------------------------------------------------------------------------------------------------------
🔷 API REFERENCE
🔸 Model Setup
sequential(name) : Creates an empty `Sequential` model.
compileConfig() : Creates a model configuration object.
build(rng) : Builds model parameters with a deterministic random stream.
compile(config) : Builds the model when needed and applies the training configuration.
rng(seed, streamId) : Creates a deterministic random stream.
🔸 Sequential Methods
input(dimsArray, name) : Defines the input shape.
dense(units, activation, name) : Adds a fully connected layer.
qHead(actionCount, activation, name) : Adds a Q-value output head.
activation(activationKind, alpha, name) : Adds an activation block.
dropout(rate, name) : Adds dropout regularization.
layerNorm(name) : Adds layer normalization.
flatten(name) and reshape(outputDimsArray, name) : Adjust model shape metadata.
block(graphBlock) : Adds a custom `GraphBlock`.
trainOnBatch(inputTensor, targetTensor) : Runs training when the active gate allows it.
backward(targetTensor) : Accumulates gradients from the last forward pass without stepping.
step() : Applies the optimizer step to accumulated gradients.
predict(inputTensor) : Runs inference.
predictMC(inputTensor, samples) : Runs dropout-enabled Monte Carlo prediction and returns mean and variance.
evaluate(inputTensor, targetTensor) : Calculates loss without updating weights.
fitDataset(dataset) and fitRollingDataset(dataset, targetOffset) : Train through dataset adapters.
getWeightsArray() and setWeightsArray(weightsArray) : Export and import flat model weights.
softUpdateFrom(sourceModel, tau) : Soft-update parameters from another model.
🔸 CompileConfig Methods
optimizer(optimizerState) : Sets the optimizer.
schedule(scheduleState) : Sets the learning-rate schedule.
loss(lossKind) : Sets the training loss.
reduction(reductionKind) : Sets loss reduction behavior.
metric(metricKind) : Adds a metric.
batchSize(size) , epochsPerBar(count) , evalStride(stride) , and historyLength(length) : Store batch and cadence preferences, and set the metric history length.
clipNorm(value) and clipValue(value) : Apply gradient clipping.
gradAccumSteps(steps) : Accumulates gradients before stepping.
withTrainingGate(enabled) : Restricts training to the last confirmed historical bar when enabled.
trainEveryCall() : Allows training whenever `.trainOnBatch()` is called.
presetPriceRegression() , presetReturnRegression() , presetBinaryDirection() , presetBinaryDirectionLogits() , presetQValues() , and presetSharpe() : Apply common loss and metric presets.
🔸 Datasets
windowDataset(featureCount, targetCount, maxRows, name) : Stores flat feature and target rows.
rollingDataset(timeSteps, featureCount, targetCount, maxRows, name) : Stores time-series windows.
pushRow(featureArray, targetArray) : Adds one validated row.
pushBuilderRow(featureBuilder, targetArray) : Adds a row from a `FeatureBuilder`.
pushNextReturnRow(featureBuilder, currentValue, futureValue) : Adds a next-return target.
pushNextDirectionRow(featureBuilder, currentValue, futureValue, threshold, zeroOne) : Adds a direction target.
ready(minRows or minWindows, targetOffset) and size() : Check dataset readiness.
lastBatch(batchSize) : Returns the most recent scaled rows from a `WindowDataset`.
toBatch() : Returns all rows from a `WindowDataset`.
unrollBatch(targetOffset) : Returns all rolling windows from a `RollingDataset`.
trainBatch(validationRows or validationWindows, targetOffset) : Returns the training side of the split.
validationBatch(validationRows or validationWindows, targetOffset) : Returns the validation side of the split.
setInputScaler(kind) , setTargetScaler(kind) , scaleInput(tensor) , scaleTarget(tensor) , and inverseScaleTarget(tensor) : Configure and apply scaling.
clear() : Clears stored rows.
🔸 Tensor, Matrix, and Feature Helpers
scalar(value) , vector(valuesArray) , matrix2d(rows, cols, fillValue) , zeros(shape) , ones(shape) , and full(shape, fillValue) : Create tensors.
shapeFromDims(dimsArray) : Creates a shape.
matrixTensor(tensor) , matrixTensor2d(rows, cols, fillValue) , and matrixTensorFromMatrix(sourceMatrix) : Create matrix tensors.
reshape(dimsArray) , flatten() , row(rowIndex) , get1d(index) , sum() , mean() , variance() , normL2() , argmax() , and dot(other) : Tensor methods.
matmul() , transpose() , add() , subtract() , multiply() , divide() , scale() , activate() , softmax() , sliceRows() , sliceCols() , concatRows() , concatCols() , globalAvgPool1d() , and globalMaxPool1d() : MatrixTensor methods.
featureBuilder(name) , push(value, featureName) , addFeature(value, featureName) , toTensor(tensorName) , toArray() , size() , and clear() : Feature row helpers.
🔸 Scalers, Optimizers, and Schedules
zScoreScaler() , minMaxScaler() , runningZScoreScaler() , and noneScaler() : Standalone scaler states.
fit(tensor) , partialFit(tensor) , transform(tensor) , and inverseTransform(tensor) : Scaler methods.
sgd(learningRate) , momentum(learningRate, momentum) , rmsprop(learningRate, rho, epsilon) , adam(learningRate, beta1, beta2, epsilon) , and adamW(learningRate, beta1, beta2, epsilon, weightDecay) : Optimizers.
constantSchedule(learningRate) , stepDecay(baseLearningRate, decaySteps, gamma) , cosineDecay(baseLearningRate, minLearningRate, decaySteps) , and warmupCosine(baseLearningRate, minLearningRate, warmupSteps, decaySteps) : Schedules.
currentRate(stepCount) : Reads a schedule's learning rate at a step.
paramBank() , append() , zeroGrad() , globalGradNorm() , step(optimizerState) , and softUpdateFrom(sourceBank, tau) : Low-level parameter bank utilities.
🔸 Losses and Metrics
mse() , mae() , huber() , logCosh() , binaryCrossEntropy() , binaryCrossEntropyFromLogits() , categoricalCrossEntropy() , softmaxCrossEntropyFromLogits() , directionalLoss() , quantileLoss() , multiHorizonWeighted() , and sharpeLoss() : Direct loss helpers.
metricValue(metricKind, predictionTensor, targetTensor) : Direct metric helper.
earlyStopper(patience, minDelta) , update(validationLoss) , and reset() : Validation stopping helper.
nextReturnValue(currentValue, futureValue) and nextDirectionValue(currentValue, futureValue, threshold, zeroOne) : Common target helpers.
🔸 GraphBlock Operations
graphBlock(name) : Creates a custom trainable graph block.
input() , param() , constScalar() , constMatrix() , and output() : Define graph inputs, parameters, constants, and output metadata.
matmul() , add() , subtract() , multiply() , divide() , scale() , activate() , softmax() , transpose() , layerNorm() , and dropout() : NeuraLib graph math.
causalMask() , sliceRows() , concatRows() , sliceCols() , concatCols() , reduceRows() , and reduceCols() : Structural graph operations.
globalAvgPool1d() , globalMaxPool1d() , attentionScore() , attentionApply() , conv1d() , scanLstm() , and scanGru() : Sequence and architecture primitives.
🔸 NeuraLib_Models API
prioritizedReplayBuffer(featureCount, targetCount, maxRows, name) : Creates a replay buffer.
pushExperience(featureRowArray, targetRowArray, priority) , sampleBatch(batchSize, alpha, beta, seed) , updatePriority(index, priority) , toBatch() , ready(minRows) , size() , and clear() : Prioritized Experience Replay helpers.
pushPositionalEncoding(position, dimensions, maxPeriod, featurePrefix) : Adds positional encoding values to a `FeatureBuilder`.
residualDense() , duelingQHead() , conv1d() , temporalConvStack() , globalAvgPool1d() , globalMaxPool1d() , lstm() , gru() , selfAttention() , multiHeadSelfAttention() , crossAttention() , transformerEncoder() , transformerEncoderStack() , and transformerDecoder() : NeuraLib_Models `Sequential` methods.
NeuraLib is for Pine Script developers who want to move beyond fixed formulas and experiment with real neural network workflows directly inside PulseWire. It is a research framework, not a guarantee of market performance. Use validation, avoid lookahead, control risk, and keep models small enough for Pine's execution limits.
All the diagrams in this publication are rendered natively on PulseWire using Pine3D
----------------------------------------------------------------------------------------------------------------
This work is licensed under (CC BY-NC-SA 4.0) , meaning usage is free for non-commercial purposes given that Alien_Algorithms is credited in the description for the underlying software. For commercial use licensing, contact Alien_Algorithms
Pine3D: A Native 3D Graphical Rendering EnginePine3D is a full 3D rendering engine for PulseWire, powered by Pine Script™ v6.
Pine3D pushes forward the frontier of PulseWire 3D rendering capabilities, providing a fully fledged graphical engine under an intuitive, chainable, object oriented API. Build meshes, transform them in world space, light them, cast shadows, project them through a perspective camera, and render the result directly on your chart, all without ever bothering about trigonometry synchronization or optimization.
The library brings forth a streamlined process for anyone that wishes to visualize data in 3D, without needing to know anything about the complex math that has previously gatekept such indicators. Pine3D does all the heavy lifting, including extreme optimization techniques designed for production ready indicators.
The entire API is chainable and tag addressable, so spawning a mesh, registering it, pointing the camera at it, and rendering the frame is a four line affair:
Mesh mybox = cube(40.0, color.orange).setTag("hero").rotateBy(0.0, 45.0, 0.0)
scene.add(mybox)
scene.lookAt("hero")
render(scene)
🔷 SURFACES: CONTOUR BAND RENDERING
Pine Script imposes a hard ceiling of 100 polylines and 500 lines per indicator . On the surface this looks fatal for dense 3D meshes: every triangle drawn naively burns one of those 100 slots, or two of the 500, and the budget evaporates within a few hundred faces.
The conventional escape hatch is strip stitching , tracing a polyline forward along one row of a grid and back along the next, packing a ribbon of quads into a single drawing slot. It buys a meaningful multiplier, but it pays for that multiplier with two structural constraints baked into the geometry itself:
One color per strip. A polyline carries a single stroke and fill color, so every cell along the ribbon must share the same shade. The moment you want per cell lighting, contour banding, or value driven gradients, every color change forces a new polyline and the budget collapses.
One contiguous ribbon per slot. Strips can only describe topologically connected runs of cells. Disjoint regions, holes, islands, and value clustered fragments scattered across the surface each demand their own polyline.
Pine3D breaks both constraints at once.
At the core of the engine sits an innovation that redefines the limits for visual fidelity: contour band rendering using degenerate bridge stitching . The technique quantizes a surface's elevation into colored bands, then collapses every cell that falls inside the same band, no matter where it sits on the screen , into one continuous, hole aware polyline path per band, threading invisible zero width bridges between disjoint islands so that a single polyline can carry thousands of polygon equivalent fragments scattered across the geometry.
The result:
A single polyline can render up to 2,000 disconnected triangle equivalents , spread across arbitrarily separated regions of the surface.
Theoretical ceiling of around 200,000 disconnected faces inside the 100 polyline budget, a regime that strip based stitching cannot enter at any color count above one.
A 40 x 40 heightmap (around 3,000 triangles) renders inside the budget with full per band contour coloring and room to spare. Stress harnesses have run 40 x 80 grids .
Each band's path is depth sorted and near plane culled, and cached between bars , so once geometry is built only the screen space projection runs per frame.
This algorithm enables scenes with extreme detail relative to the 100 polyline limit, and shifts the optimization focus from "drawing limits" to "CPU limits", which Pine3D natively handles with aggressive caching at every layer of the pipeline. The contour technique is currently integrated into the surface() function, with the same compression strategy generalizable to any mesh class and ultimately full scene rendering in future versions.
Non-uniform grids out of the box. surface() accepts optional axisX and axisZ arrays that override the default uniform spacing with custom column and row positions. This means logarithmic strike spacing on an option volatility surface, irregular timestamp spacing on a market depth heatmap, or any other non-evenly-sampled grid renders correctly without resampling the data first. The contour band engine, axis ticks, and gridBox cage all snap to the custom positions automatically.
A full contour surface is just a handful of lines; the damped ripple below builds once and never needs updating:
//@version=6
indicator("Pine3D - Contour Surface", overlay = false, max_polylines_count = 100, max_lines_count = 500, max_labels_count = 500)
import Alien_Algorithms/Pine3D/1 as p3d
var p3d.Scene scene = p3d.newScene()
var p3d.Mesh heatmap = na
if barstate.isfirst
// Damped cosine ripple
int N = 20
matrix data = matrix.new(N, N, 0.0)
for r = 0 to N - 1
for c = 0 to N - 1
float dx = c - (N - 1) / 2.0
float dz = r - (N - 1) / 2.0
float d = math.sqrt(dx * dx + dz * dz) * 0.7
data.set(r, c, math.cos(d) * math.exp(-d * 0.12) * 50.0)
heatmap := p3d.surface(data, 200.0, color.blue, color.red, 24)
.gridBox()
.gridLabels(color.white, "X", "Amplitude", "Z")
scene.add(heatmap)
scene.camera.orbit(35.0, 25.0, 380.0)
if barstate.islast
p3d.render(scene, lighting = true)
🔷 TRAIL3D: STREAMED OSCILLATOR PATHS
Trail3D is a first class streaming primitive built for visualizing two correlated time series as a 3D ribbon evolving through time. You give it a rolling buffer capacity and push (u, v) samples bar by bar; the primitive maintains the buffer, builds the ribbon geometry, and renders it inside a normalized bounding cube so the path always fits cleanly in view regardless of the underlying data range.
Under the hood, Trail3D is a coordinated bundle of polylines: one for the main ribbon, two for optional shadow projections onto the back wall and floor, and one for the wireframe cage. All four are depth sorted and occlusion clipped against the rest of the scene, and the primitive auto normalizes incoming samples against the rolling window's min/max so streaming data always fills the cube without manual scaling.
This enables a class of visualizations that would otherwise require dozens of polylines and manual buffer management: phase space portraits, Lissajous figures, oscillator pair correlations, attractor trajectories, and any "two indicators evolving together over time" study. The demo above shows a sine and cosine pair pushing samples each bar to trace a clean spiral inside the cage, the same pattern you would use to plot RSI vs MFI, momentum vs volatility, or any custom (u, v) signal pair.
A full streamed scene is a handful of lines:
//@version=6
indicator("Pine3D - Trail3D", overlay = false, max_polylines_count = 100, max_lines_count = 500, max_labels_count = 500)
import Alien_Algorithms/Pine3D/1 as p3d
var p3d.Scene scene = p3d.newScene()
var p3d.Trail3D trail = na
if barstate.isfirst
trail := p3d.trail3D(220.0, 200, color.yellow)
.cage(true)
.axisLabels("sin", "cos", color.white)
trail._uProj.col := #00ffff69
trail._vProj.col := #ff00ff71
scene.add(trail)
scene.camera.orbit(215.0, 20.0, 360.0)
float phase = bar_index * 0.15
float sinX = math.sin(phase) * 100.0
float cosY = math.cos(phase) * 100.0
if barstate.isconfirmed
trail.pushSample(sinX, cosY)
p3d.render(scene)
🔷 BARS3D: CATEGORICAL 3D BAR CHARTS
bars3D() turns any series of values into a fully lit, depth sorted 3D bar chart in a single call. Each bar is height mapped to its value, color graded between a low and high color, and packed into one combined mesh with per bar depth grouping so individual bars sort correctly even inside the merged geometry. The companion updateBars() mutator refreshes heights, colors, and labels in place every bar without rebuilding geometry, making it suitable for live rankings, rolling windows, and animated comparisons.
The chainable barLabels(catNames, valNames) helper attaches category labels at the base of each bar and value labels at the top, both depth sorted with the rest of the scene. Category labels are set once at build time, while value labels can be passed to updateBars(values, valLabels = ...) each frame to reflect live data. Combined with wireGrid() for the floor and a contour surface() in the background, bars3D() becomes the centerpiece of dashboards comparing assets, sectors, timeframes, or any categorical metric.
Negative values are handled automatically: bars below zero extrude downward from the base plane with reversed face winding, so signed series like PnL, delta, or momentum histograms render correctly without any extra setup.
A complete labeled bar chart is just a few lines:
//@version=6
indicator("Pine3D - Bars3D", overlay = false, max_polylines_count = 100, max_lines_count = 500, max_labels_count = 500)
import Alien_Algorithms/Pine3D/1 as p3d
var p3d.Scene scene = p3d.newScene()
var p3d.Mesh bars = na
array values = array.from(volume - volume , volume - volume , volume - volume , volume - volume , volume - volume , volume - volume )
array names = array.from("ΔV0", "ΔV-1", "ΔV-2", "ΔV-3", "ΔV-4", "ΔV-5")
if barstate.isfirst
bars := p3d.bars3D(values, 30.0, 30.0, 10.0, color.blue, color.red, 200.0)
.barLabels(names)
scene.add(bars)
p3d.wireGrid(scene, 300.0, 300.0, 6, 6, color.new(color.gray, 80))
scene.camera.orbit(215.0, 25.0, 360.0)
if barstate.islast
bars.updateBars(values)
p3d.render(scene, lighting = true)
Omitting valLabels in updateBars() tells the engine to auto format each numeric value via str.tostring() . Pass valLabels only when you need custom strings.
🔷 SCATTER CLOUDS: POINTS IN 3D SPACE
Pine3D treats scatter clouds as a first class use case without needing a dedicated scatter API. Because Label3D is the primitive and scene.add(array) is a single batch operation, you can scatter up to 500 points anywhere in 3D space, each with independent color, symbol, size, and tooltip , and have them depth sorted and occlusion clipped against the rest of the scene automatically.
Each point is a fully addressable Label3D with mutable fields. You can change position , textColor , bgColor , labelStyle (any label.style_* glyph including circles, squares, diamonds, triangles, crosses, arrows, flags), labelSize (any size.* preset), and text per point per bar. The renderer reads these mutations every frame, so animation is just direct field assignment.
This unlocks a wide class of visualizations: clustered data scatter, K means visualizations, particle systems, parametric surfaces sampled as point clouds, gradient colored attractors, multi class classification overlays, and structured curves like the demo above. The double helix demo plots two intertwined parametric strands as ~500 points with alternating colors and per point sizing, all inside the standard scene.add(array) pipeline.
The pattern is straightforward: build the array once in barstate.isfirst , add it to the scene, then mutate point fields per bar to animate.
//@version=6
indicator("Pine3D - Scatter Cloud", overlay = false, max_polylines_count = 100, max_lines_count = 500, max_labels_count = 500)
import Alien_Algorithms/Pine3D/1 as p3d
var p3d.Scene scene = p3d.newScene()
var array points = array.new()
if barstate.isfirst
for i = 0 to 499
p3d.Vec3 pos = p3d.vec3(0.0, 0.0, 0.0)
points.push(p3d.Label3D.new(position = pos, txt = "•"))
scene.add(points)
scene.camera.orbit(35.0, 20.0, 400.0)
if barstate.islast
for i = 0 to points.size() - 1
float t = i * 0.05 + bar_index * 0.01
p3d.Label3D pt = points.get(i)
pt.position := p3d.vec3(80.0 * math.cos(t), i * 0.4 - 100.0, 80.0 * math.sin(t))
pt.textColor := i % 2 == 0 ? color.aqua : color.fuchsia
p3d.render(scene)
----------------------------------------------------------------------------------------------------------------
🔷 TWO LAYER ARCHITECTURE
Pine3D ships as a clean, two layer library:
🔸 Layer 1 - DIY API. First principle building blocks ( Vec3 , Mesh , Camera , Light , Scene , plus world space overlay primitives) for total creative control. Author your own geometry, camera behavior, lighting setup, and scene graph from scratch.
🔸 Layer 2 - High Level Helpers. Production ready wrappers like surface() , bars3D() , trail3D() , updateBars() , updateSurface() , sphere() , torus() , cylinder() , and wireGrid() , plus chainable contour helpers gridBox() and gridLabels() that wrap the primitives into a few lines of code. Scatter clouds use the standard Label3D primitive directly.
The object model is chainable and scene oriented, so complex setups still read cleanly.
🔷 FEATURE LIST
Contour Surface Rendering - The most powerful 3D surface engine ever released for Pine Script. Render tens of thousands of polygon equivalent faces using a single polyline per contour band, delivering smooth, continuous terrain with natural ridges and valleys.
Adaptive Rail Sharing - Solid meshes drawn with the default linefill backend reuse one edge line between adjacent coplanar faces, averaging roughly 1.6 lines per face instead of the naive two, pushing practical mesh capacity up to ~360 faces depending on topology.
Interior Face Culling on Merge - mergeMeshes(meshes, removeInterior = true) detects coincident faces with opposing normals and strips them, so voxel style scenes (stacked cubes, block walls, lattice geometry) ship only their exterior shell and spend no budget on hidden interior faces.
True Perspective Camera System - Full 3D camera with position, target, fov, and orbit() controls. Supports cinematic camera movement, lookAt by mesh tag, and realistic depth.
Real Time Lighting and Shadows - Directional and point lights with configurable ambient, shadow strength, self shadowing, and a spatial grid acceleration structure for fast shadow queries.
High Performance Update System - updateSurface() and updateBars() let you animate massive datasets bar by bar without rebuilding geometry, keeping CPU usage minimal.
Rich Primitive Library - Cubes, cuboids, spheres, cylinders, tori, pyramids, planes, discs, circles, custom meshes, and the groundbreaking bars3D() with automatic labels.
Streamed Trail Primitive - trail3D() maintains a rolling buffer of (u, v) samples and renders them as a 3D ribbon inside a bounding cube, with optional projections onto the back wall and floor and a wireframe cage.
Depth Sorted Overlays - 3D labels, lines, polylines, wire grids, and trails, all correctly occluded and painter sorted against the rest of the scene.
Professional Contour Helpers - gridBox() and gridLabels() automatically add clean bounding boxes and axis titles, ticks, and series names that refresh on every updateSurface() call.
Tag Based Scene Graph - Every Mesh , Label3D , Line3D , and Polyline3D can carry a string tag. Scene exposes getMesh() , getLabel() , getLine() , getPolyline() , lookAt() , and remove() by tag, turning your scene into a lookup by name graph instead of an index juggling exercise.
Chainable, Intuitive API - Everything is designed for maximum readability and speed of development. Build complex scenes in just a few lines.
Production Ready Optimizations - World vertex caching, view projection caching, face preprocessing cache, shadow grid cache, and contour geometry cache, all managed automatically.
----------------------------------------------------------------------------------------------------------------
🔷 THE RENDERER
Every frame is produced by a single call to render(scene, ...) . The renderer runs the full pipeline: world transform, camera transform, back face culling, occlusion culling, depth sort, directional or point lighting with shadows, and perspective projection.
⚠ render() clears the entire chart drawing pool at the start of every call - every polyline , line , label , and linefill on the chart is deleted before Pine3D redraws, not just the ones it created. If you mix Pine3D with manual label.new() , line.new() , or similar calls, those drawings must be emitted after render() or they will be wiped every frame.
🔸 Setup Requirements. Pine3D consumes polylines, lines, and labels simultaneously, so your indicator() declaration must raise all three budgets, and the library must be imported under an alias:
indicator("My 3D Scene", overlay = false,
max_polylines_count = 100,
max_lines_count = 500,
max_labels_count = 500)
import Alien_Algorithms/Pine3D/1 as p3d
🔸 render() parameters.
maxFaces (int, default 100). Hard cap on solid faces drawn per frame. Contour bands, wireframe edges, labels, lines, and overlay polylines are not counted against this cap, and are bounded only by PulseWire's global 100 polyline / 500 line / 500 label budgets.
culling (bool, default true). Enable back face culling.
lighting (bool, default false). Enable diffuse shading. Reads scene.light if set; otherwise falls back to the render() args.
lightDir (Vec3). Overrides scene.light.direction when provided. Points toward the light.
ambient (float, default 0.3). Minimum brightness for shadowed faces (0.0-1.0).
wireframe (bool, default false). Force outline only output for the entire scene.
occlusion (bool, default true). Sparse raster pass that drops hidden faces before drawing. Major perf win on dense scenes.
occlusionRaster (int, default 768). Raster resolution of the occlusion buffer. Lower = faster but coarser; higher = stricter hidden face rejection.
Explicit render() args always win over scene.light , which makes render() the right place for ad hoc, per frame lighting tweaks.
----------------------------------------------------------------------------------------------------------------
🔷 MESH DRAWING MODES
Two independent axes control how a mesh appears on the chart:
🔸 Style (via mesh.setStyle(...) ) - what gets drawn:
"solid" . Filled faces. Default.
"wireframe" . All edges, no fill. Shows interior geometry.
"wireframe_front" . Only front facing edges. Cleaner silhouette for convex meshes.
🔸 Draw Mode (via mesh.drawMode ) - which PulseWire primitive carries the solid faces:
"linefill" (default). Uses the line and linefill budgets. An adaptive rail sharing optimization reuses one edge line between adjacent coplanar faces, pushing practical capacity up to ~360 faces per mesh depending on topology. Supports in place updates via updateSurface() and updateBars() . Rails are drawn transparent, so solid faces in this mode have no visible outline - use a wireframe style or "poly" drawMode if you need stroked edges. Recommended for all new code.
"poly" . Legacy polyline backend. Capacity ~100 faces, no in place updates, but renders the face outline using mesh.lineStyle and mesh.lineWidth . Use only when you need styled solid face outlines.
Wireframe styles always render with line primitives regardless of drawMode. Stroke width and style on edges (and on poly mode face outlines) come from mesh.lineWidth and mesh.lineStyle , which you mutate by direct field assignment.
----------------------------------------------------------------------------------------------------------------
🔷 QUICK START
The best practice lifecycle is simple:
Create one persistent Scene with newScene() .
Build meshes and helper overlays once in barstate.isfirst .
On later bars, mutate objects in place with transforms or helper mutators like updateBars() and updateSurface() .
Call render(scene, ...) once per frame. It automatically clears the previous chart drawings.
A complete, lit, animated 3D scene is still a handful of lines:
//@version=6
indicator("My First 3D Scene", overlay = false, max_polylines_count = 100, max_lines_count = 500, max_labels_count = 500)
import Alien_Algorithms/Pine3D/1 as p3d
var p3d.Scene scene = p3d.newScene()
var p3d.Mesh sun = na
if barstate.isfirst
scene.setLightDir(1.0, -1.0, 0.5).setAmbient(0.3)
sun := p3d.sphere(50.0, 16, 12, color.orange).setTag("sun")
scene.add(sun)
p3d.wireGrid(scene, 300.0, 300.0, 6, 6, color.new(color.gray, 80))
scene.camera.orbit(35.0, 25.0, 220.0)
if barstate.islast
sun.rotateBy(0.0, 1.5, 0.0)
p3d.render(scene, lighting = true)
----------------------------------------------------------------------------------------------------------------
🔷 RECOMMENDED USAGE PATTERN
Use your Scene and major meshes in var .
Build geometry once in barstate.isfirst .
Use updateSurface() and updateBars() on later bars instead of rebuilding meshes.
Use scene level helpers like wireGrid() when you want overlays added immediately.
Use trail3D() when you want a streamed oscillator style path with built in wall projections and cage geometry.
For scatter clouds, build an array once, hand it to scene.add(pts) , then mutate pt.position , pt.textColor , etc. each bar to animate.
Use mesh level gridBox() and gridLabels() (contour) and barLabels() (bars) to attach overlays to the mesh setup chain. They are drained into the scene by scene.add(mesh) .
🔷 CONSIDERATIONS
scene.clear() vs render(). scene.clear() removes objects from the scene graph (meshes, labels, lines, polylines). render() only clears the previous frame's PulseWire drawings and redraws from the current scene graph. You almost never need scene.clear() in the build once and update pattern.
Global scope series for updateSurface() / updateBars(). If your data uses Pine's history operator ( ) or calls functions like ta.rsi() , ta.atr() , request.security() , those must be declared at global scope so Pine tracks their bar by bar history. Calling them inside barstate.islast produces inconsistent results or compiler errors.
gridLabels() tick values auto refresh. When you call updateSurface() , any tick value labels created by gridLabels() are automatically updated to reflect the new data range. Axis titles and positions stay constant. You don't need to rebuild them.
barLabels() value labels via updateBars(). Create category labels once with mesh.barLabels(catNames) at build time, then pass valLabels to updateBars() on each frame. Value labels are refreshed automatically. Don't call barLabels() again.
Lighting convenience methods are chainable. scene.setLightDir() , setLightPos() , setLightMode() , setAmbient() , setShadowStrength() , and showLightSource() all return Scene and can be chained: scene.setLightMode("point").setLightPos(0, 200, 150).setAmbient(0.25) .
Mesh transforms return Mesh. moveTo() , moveBy() , rotateTo() , rotateBy() , scaleTo() , scaleUniform() , setTag() , setStyle() , setColor() , show() , hide() all return Mesh for chaining: mesh.moveTo(0, -20, 0).rotateTo(0, 45, 0).setStyle("solid") .
Degrees vs radians. rotateTo() and rotateBy() on Mesh expect degrees. The low level Vec3.rotateX/Y/Z() methods expect radians.
scene.lookAt() is tag only. scene.lookAt(t) accepts a string tag and points the camera at that mesh. To aim the camera at an arbitrary Vec3 , call scene.camera.lookAt(vec) directly.
remove(tag) removes one object. The search order is meshes, then labels, then lines, then polylines, and the first hit wins. Avoid reusing tags across primitive types if you intend to delete by tag.
Shadow grid acceleration is directional light only. The spatial shadow grid is only built when lightMode == "directional" . Point lights fall back to a linear O(M) scan, so heavy shadow scenes are fastest in directional mode.
guiShift and yOffset. scene.guiShift and scene.yOffset position the 3D viewport on the chart without consuming historical bar slots. Increase guiShift to push the scene rightward into future bar space; adjust yOffset to slide it vertically in price units.
bar_time projection. All chart drawings are emitted with xloc.bar_time , so the scene can sit arbitrarily far left or right of bar_index without forcing Pine to extend its history buffer. This is what keeps the engine stable on long charts and future projected scenes.
barLabels() without values. When you call mesh.barLabels(catNames) and omit value labels, every later updateBars(values) auto formats the numeric values via str.tostring() . Pass valLabels only when you need custom strings.
Direct mesh.vertices mutation requires invalidateCache(). Transform mutators ( moveTo , rotateBy , scaleTo , etc.) invalidate the world vertex cache on their own. Only raw index writes like mesh.vertices.set(i, newVec) need a manual mesh.invalidateCache() call to force re-projection. Skipping it will make the renderer draw stale geometry.
Drawing budgets fail silently. If a scene emits more than 100 polylines, 500 lines, or 500 labels in a single frame, PulseWire silently drops the overflow without raising a runtime error. Missing geometry almost always means a budget overrun - lower maxFaces , drop a contour level, or simplify overlay primitives to bring the frame back inside the caps.
render() deletes non Pine3D drawings too. Every render() call clears polyline.all , line.all , label.all , and linefill.all before redrawing. Any manual label.new() , line.new() , etc. issued before render() in the same frame will be wiped. Issue custom drawings after the render call if you need them to persist.
mergeMeshes() preserves depth grouping. When every source mesh passed into mergeMeshes() has the same vertex and face count (e.g. identical primitives in a voxel grid), the merged mesh auto derives depth group boundaries so the combined geometry still sorts correctly per original instance. Mixing primitives with different topologies disables the grouping.
CPU timeouts: knobs to turn. Pine Script enforces a per bar execution budget, and dense scenes can trip it before the drawing budget ever does. If a scene compiles but times out at runtime, reach for these levers in order: lower occlusionRaster (e.g. 768 -> 384) for the biggest single perf win, reduce maxFaces to cap the solid face pool, drop levels on contour surfaces, simplify sphere/torus segment counts, and gate heavy work behind barstate.islast so history bars only build geometry rather than render it.
----------------------------------------------------------------------------------------------------------------
🔷 MORE EXAMPLES
The following scenes were all built entirely in Pine Script™ v6 using Pine3D as the rendering layer. They exist to demonstrate that the library is a real engine capable of complex, production grade visualizations.
🔸 4D Hypercube (Tesseract). A rotating tesseract, projected from 4D to 3D to 2D in real time using a custom 4D rotation matrix layered on top of Pine3D's standard projection pipeline.
🔸 Solar System. Following the publication of my 3D Solar System back in 2024, which introduced new graphical rendering concepts into Pine Script, we have seen a wave of various interpretations of the underlying vector classes, ranging from tutorials to niche specific integrations using hardcoded math. It became clear that a unified architecture was needed, one that would lower the barrier to entry while simultaneously handling the optimization process, which is both complex and error prone to do manually.
That architecture is what Pine3D delivers. Below is a re-creation of the classic 3D Solar System rebuilt entirely on top of the library. It uses a fraction of the original code , renders roughly 5x faster , and adds real lighting cast directly from the Sun , all while consuming only a third of the available drawing budget thanks to the occlusion and culling mechanisms Pine3D handles out of the box.
----------------------------------------------------------------------------------------------------------------
🔷 API REFERENCE
🔸 Top Level Entry Points. newScene() creates a ready to use Scene with a default camera and light. render(scene, ...) draws the current frame and auto clears the previous frame's chart drawings; see the Renderer section above for the full parameter list. vec3(x, y, z) creates a Vec3. colorBrightness() is an exported color utility helper.
🔸 Mesh Factories.
Primitives - cube() , cuboid() , pyramid() , plane() , sphere() , cylinder() , torus() , grid() , disc() , circle() for ready made geometry.
customMesh(verts, faces) - Low level escape hatch for authoring your own topology.
mergeMeshes(meshes, tag, removeInterior) - Bakes transforms and combines many meshes into one. With removeInterior = true , coincident faces with opposing normals (e.g. shared walls between adjacent cubes in a grid) are culled so only the exterior shell survives, a major optimization for dense voxel style scenes.
surface(heights, size, lowCol, highCol, levels, axisX, axisZ) - Creates a contour surface mesh.
bars3D(values, barWidth, barDepth, spacing, lowCol, highCol, maxHeight) - Creates a combined 3D bar chart mesh; add labels with the chainable barLabels(names, values) method.
🔸 UDT Constructors. Overlay primitives and face descriptors are plain UDTs. Because these types have many fields, always instantiate them with named arguments rather than positional, e.g. Label3D.new(position = pos, txt = "•") :
Face - fields: vi (array of vertex indices into the parent mesh), col . Used when authoring customMesh() topology; every face must have at least 3 indices and should be planar.
Label3D - fields: position , txt , textColor , bgColor , labelStyle , labelSize , fontFamily , tooltip , visible , tag . Only position is required.
Line3D - fields: start , end , col , width , visible , tag , lineStyle .
Polyline3D - fields: points , col , fillColor , width , closed , visible , tag , lineStyle .
Vec3.new(x, y, z) or the vec3(x, y, z) shorthand.
🔸 Trail Primitive. trail3D(size, capacity, trailCol, minSamples) creates a streamed Trail3D primitive with a main trail, two projection polylines, and a cage polyline. capacity is internally clamped to 300 samples to keep the rolling buffer inside Pine's execution budget; passing a larger value silently resolves to 300. minSamples (default 60) is the sample count at which the cage reaches its full cube width: below that the cage stays cube shaped and samples stretch across it; above that the cage grows rightward at a fixed step until capacity is hit. scene.add(trail) registers the sub primitives into the scene. Trail3D methods: pushSample() , axisLabels() , cage() , moveTo() , show() , hide() .
🔸 Mesh Methods.
Transform - moveTo() , moveBy() , rotateTo() , rotateBy() , scaleTo() , scaleUniform() .
Appearance - setColor() , setFaceColor() , setStyle() , show() , hide() , setTag() .
Stroke styling (direct) - mesh.lineWidth := 3 and mesh.lineStyle := line.style_dashed control width and style of every visible mesh edge in wireframe modes and the outline of solid faces in drawMode = "poly" .
Shadow opt out (direct) - mesh.castShadow := false excludes the mesh from shadow casting while still receiving light. Useful for ghost overlays, debug geometry, or semi transparent meshes you do not want occluding the scene.
Lifecycle - clone() , faceCount() , invalidateCache() .
Data mutation - updateSurface() and updateBars() refresh persistent meshes in place. updateBars() refreshes any bar label positions automatically; pass catLabels / valLabels to also update the text.
Contour helpers - gridBox() and gridLabels() queue overlays on the mesh and hand them to the scene when you call scene.add(mesh) .
Bar helpers - barLabels() is chainable on a bars3D() mesh and queues its category and value labels for the next scene.add(mesh) .
Note: rotateTo() and rotateBy() expect degrees. The low level Vec3.rotateX/Y/Z() methods work in radians.
🔸 Scene Methods.
Lighting - setLightDir() , setLightPos() , setLightMode() , setAmbient() , setShadowStrength() , showLightSource() .
Scene graph - add(mesh) , add(label) , add(array) , add(line) , add(polyline) , add(trail) , remove(index) , remove(tag) , clear() .
Lookup and navigation - getMesh() , getLabel() , getLine() , getPolyline() , lookAt() , totalFaces() .
Cache control - invalidateLightCache() after mutating light direction or scene bounds externally; invalidateAllCaches() to also invalidate every mesh's world vertex cache (use after directly mutating mesh.vertices ).
Note: scene.clear() clears the scene graph itself. render() only clears the previous frame's PulseWire drawings.
🔸 Camera Methods. setPosition(x, y, z) moves the camera. lookAt(x, y, z) / lookAt(vec3) points at a world space target. orbit(angleX, angleY, distance) does a spherical orbit around the current target. setFov(val) sets the perspective scale factor. Camera fields ( position , target , fov ) are also directly mutable via assignment when you need to tune them outside the provided setters, e.g. scene.camera.fov := 1200.0 .
🔸 Light Field Mutation. In addition to the scene level convenience setters, every field on scene.light is directly mutable for fine grained tuning: scene.light.selfShadow := true enables self shadowing, scene.light.shadowBias := 0.2 adjusts the shadow acne offset, scene.light.shadowStrength and scene.light.ambient are also exposed. Mutate them after newScene() or between frames; the renderer reads them every call.
🔸 Vec3 Methods. Core math: add() , sub() , scale() , negate() , dot() , cross() , length() , normalize() , distanceTo() , lerp() . Rotation and helpers: rotateX() , rotateY() , rotateZ() , copy() , toString() .
🔸 Overlay Primitive Methods.
Label3D - moveTo() , moveBy() , setText() , setTextColor() , setTooltip() , show() , hide() , setTag() .
Line3D - setStart() , setEnd() , setPoints() , setColor() , show() , hide() , setTag() .
Polyline3D - setColor() , show() , hide() , setTag() .
Every UDT field is mutable via direct assignment for properties without a chainable setter:
Label3D - bgColor , labelStyle (label.style_*), labelSize (size.*), fontFamily (font.family_*), visible .
Line3D - width , lineStyle (line.style_solid / _dashed / _dotted / _arrow_left / _arrow_right / _arrow_both), visible .
Polyline3D - width , lineStyle (line.style_solid / _dashed / _dotted only; arrow styles are not supported by PulseWire's polyline primitive), fillColor , closed , visible .
Mutations are read per frame by the renderer, so they animate freely.
🔸 High Level Scene Helpers. wireGrid(scene, w, d, divX, divZ, col) adds a depth sorted ground grid. scene.add(array) adds a batch of labels in one call - the idiomatic way to push a scatter cloud into the scene.
🔸 Mesh Level Chainable Overlays. mesh.barLabels(names, values, ...) adds category and value labels on a bars3D() mesh. mesh.gridBox(col, divs) adds a wireframe bounding box cage on a surface() mesh. mesh.gridLabels(col, xName, yName, zName, ticks, fmt) adds axis titles and tick value labels on a surface() mesh; tick values auto refresh on updateSurface() . All three are queued on the mesh and drained into the scene by scene.add(mesh) .
----------------------------------------------------------------------------------------------------------------
This work is licensed under (CC BY-NC-SA 4.0) , meaning usage is free for non-commercial purposes given that Alien_Algorithms is credited in the description for the underlying software. For commercial use licensing, contact Alien_Algorithms
TASC 2026.05 The AutoTune Filter█ OVERVIEW
This script implements the AutoTune Filter described by John F. Ehlers in the article "A Rolling Autocorrelation Function" from the May 2026 edition of the TASC Traders' Tips . The script analyzes rolling autocorrelation in filtered price data to calculate a band-pass filter that dynamically adjusts to apparent dominant cycles.
█ CONCEPTS
Autocorrelation function (ACF)
Autocorrelation measures the correlation of a time series with a lagged version of itself. The autocorrelation function (ACF) evaluates autocorrelation across a range of lags to gauge the extent to which values in a series vary jointly with previous values at different offsets.
The ACF can help traders identify patterns and trends in stochastic market data, characterize long-range dependence in a series, and more. In his article, Ehlers explains how the ACF can serve as a "bridge" between analysis in the time and frequency domains for identifying dominant cycles in market data.
Ehlers notes that at low lags, such as one bar, the autocorrelation in price data tends to be very high because prices don't often change dramatically from one bar to the next. As the lag increases, autocorrelation often decreases, reaching near zero for offsets at which the latest prices do not show a clear relationship with past prices.
However, he also observed that at specific lags, anticorrelation (negative correlation) can emerge, where the current values in the series move in one direction while past values move in the opposite direction. Based on this observation, he suggests that a lag with strong anticorrelation can indicate a significant cycle in the market data, where the cycle length is twice that of the analyzed lag.
To understand why this behavior can indicate significant cycles, consider a sine wave that completes a full oscillation every 20 bars. If the series is currently moving up, it will then move down 10 bars later, and then complete the cycle by moving up again 10 bars after that. The ACF of that sine wave returns a value of -1 for a lag of 10 bars, but not for other lower lags or higher lags up to 20.
In other words, a pure sine wave with a given period has perfect anticorrelation with a delayed version of itself that is offset by half of that period.
While market data does not typically behave like a pure sine wave, the same underlying principle applies: if the current prices exhibit a strong anticorrelation with previous prices at a given offset, a dominant cycle with a length of twice that offset is likely present in the current data.
AutoTune Filter
Ehlers proposes that traders can use the dominant cycle obtained via autocorrelation to set the critical period of a filter. Tuning a filter to respond most strongly to the measured cycle may promote more consistency in time alignment and help reduce destructive phase shifts.
He demonstrates one such implementation with his AutoTune Filter, an adaptive band-pass filter whose center period dynamically increments toward the dominant cycle calculated from an ACF over a given window.
The steps to calculate the AutoTune Filter are as follows:
Apply a two-pole high-pass filter to the series to reduce the effect of low-frequency (long-period) cycles on the autocorrelation calculation. The filtered series emphasizes cycles with lengths up to the specified cutoff period, and attenuates all others.
Compute the rolling ACF of the filtered data across the same window length as the filter's cutoff period.
Check the autocorrelation for each lag period, and identify the smallest lag with the lowest autocorrelation value. Multiply that lag by two to obtain the dominant cycle for the analyzed window.
If the difference between the current and previous dominant cycle is greater than two, limit the result for the current bar to two greater or less than the previous cycle's value to prevent large, sudden shifts in the filter's center period.
Finally, compute a band-pass filter using the value from step 4 as the center period.
█ USAGE
This indicator includes four display modes to visualize the AutoTune Filter's calculations:
"High-pass filter" : Plots the high-pass filtered data that the script analyzes for autocorrelation calculations.
"Min. correlation" : Plots the lowest autocorrelation value calculated for the filtered series over the analyzed window.
"Dominant cycle" : Plots the dominant cycle value that the final filter uses for its center period.
"Tuned band-pass filter" (default): Plots the final band-pass filtered result, i.e., the AutoTune filter.
Ehlers suggests that traders can identify peaks and valleys in prices for potential mean reversion signals by analyzing the rate of change in the tuned band-pass filter. If the rate of change is zero, the current price might be near a local high if the filter's value is positive, or near a local low if the value is negative.
Users can analyze the additional outputs to gain further insight into the filter's behaviors, and they can pass these plotted values to other scripts via source inputs for easy use in other custom calculations.
█ INPUTS
The indicator includes the following inputs in the "Settings/Inputs" tab:
Source: The series of values to process.
Window: The window length of the ACF calculation, and the cutoff period of the high-pass filter. The maximum possible dominant cycle length is two times this value.
Output: One of the four display modes ("High-pass filter", "Min. correlation", "Dominant cycle", or "Tuned band-pass filter").























