Most structure indicators stop once they have found the pivot. They draw the zigzag, label the high or the low, and leave the rest to you. Swing Fibonacci, an indicator by BigBeluga, adds a second layer on top: from the last confirmed pivot it grows an Archimedean spiral whose size is fixed by the market itself, and then leaves it frozen there until the structure flips again.
The result looks unusual on a chart, and it invites a misreading worth clearing up in the first paragraph. The spiral is not a forecast and it does not track price. It is a ruler. Its arms mark, in price and in bars at the same time, how far the market would travel if it kept expanding at the pace of the leg that has just been confirmed. Price wandering inside the inner coils is a market that has not yet used up its own recent range; price reaching an outer arm is a market that has.
The structure part is deliberately blunt. A new 50-bar high turns the state bullish; a new 50-bar low turns it bearish. Nothing else moves it.
What matters is what happens when the state flips, because that is the only moment the indicator commits to anything:
Those two frozen numbers are the whole geometry of the spiral. Between one flip and the next they do not change, so the figure stays exactly where it was put. This is the property that makes it usable: a shape that moved with every new bar would tell you nothing, because you could never say whether price had reached it or it had come to price.
The 50-bar minimum on the structure length is not decoration either. Shorter values make the state flip constantly, and every flip re-anchors and redraws the whole figure.
The spiral is Archimedean — the distance between consecutive coils is constant, unlike a logarithmic spiral where it grows geometrically. Walking an angle θ from 0 to arms × 180 degrees, with u running from 0 to 1 over the same walk:
x(u) = anchorBar - u * cos(θ) * radius * legLength
y(u) = anchorPrice + s * u * sin(θ) * range * radius / dY
s is −1 in an uptrend and +1 in a downtrend, which flips the spiral so that it opens on the side the market is coming from. dY is the peak-to-peak span of u × sin(θ), and it is the one quantity that cannot be known in advance: it depends on how many coils are drawn, so the curve has to be walked once to measure it before it can be walked again to draw it.
Two things fall out of those formulas that are worth stating plainly, because they change how you set the indicator up.
The number of arms cancels out. It appears in the numerator and again in the denominator on both axes. Adding coils does not make the figure bigger — it makes it denser. Eight arms and three arms occupy the same box on the chart; the difference is how many times the curve wraps around inside it.
Radius is not a size, it is a multiplier of the market’s own range. After the cancellation the total height of the spiral is exactly:
height = range(500 bars) × radius
So radius = 4 draws a figure four times as tall as everything price has done in the last 500 bars, and the candles get squeezed into a quarter of the pane. This is the parameter to touch first, and downwards. The value shipped here is 2.5, which is what the chart in the forum topic uses, and it is already generous.
The horizontal reach follows the same rule: ± radius × legLength bars around the anchor. With a long leg the spiral runs several hundred bars into the future, so the chart needs free space on its right for the outer arm to be visible.
The trace runs through a gradient from the bearish colour at the centre to the bullish colour at the outermost arm. That is a position cue, not a signal: it tells you which end of the curve you are looking at when the coils overlap, nothing more.
The dashed line from the anchor to the live price, and the Swing % reading in the panel, are the part that does carry information — that percentage is the distance travelled since the anchor, and it is the number to compare against previous legs on the same instrument.
The honest use of this indicator is as a framing tool, not a trigger. Three readings it gives cheaply:
Swing % against the anchor, compared with how far previous legs ran on the same instrument and timeframe. A leg that has already covered more than its predecessors while the state has not flipped is a stretched leg.What it will not do is time an entry. There is no rule in the construction that connects an arm of the spiral to a reaction in price — the arms are where the geometry puts them, and the geometry only knows the 500-bar range and the length of one leg.
Swing Fibonacci is one of those indicators whose visual is louder than its logic, and the two should not be confused. Underneath the figure there is a plain 50-bar structure engine and two frozen numbers. Everything else is geometry.
Used as it is meant to be — a frozen ruler anchored on a confirmed pivot, read together with the percentage travelled since that pivot — it answers a question most structure tools leave open: not where did the market turn, but how much of its own recent range has this leg already spent.
//---------------------------------------------------------------------------//
//PRC_Swing Fibonacci
//version = 1
//10.09.2026
//Ivan Gonzalez @ www.prorealcode.com
//Author: BigBeluga
//Sharing ProRealTime knowledge
//---------------------------------------------------------------------------//
//Swing structure zigzag whose last confirmed pivot anchors an Archimedean
//spiral. The spiral is a compass, not a forecast: it is drawn from the anchor
//with a fixed geometry, so its arms mark where the market would be if it kept
//expanding at the pace of the leg that has just been confirmed.
//---------------------------------------------------------------------------//
DEFPARAM drawonlastbaronly = true
//-----Swing structure-------------------------------------------------------//
swingLength = 50 //Swing Structure (50..100): how significant a high or low must be
maxMarks = 60 //Swing marks kept on the chart
//-----Spiral----------------------------------------------------------------//
spiralArms = 8 //Spirals qty (1..10): rotations around the anchor
spiralRad = 2.5 //Radius (1..8): vertical stretch AND horizontal reach
spiralRes = 300 //Segments of the spiral, 3 or more (original resolution: 300)
maxFwd = 500 //Bars drawn to the right of the last one (original cut-off)
//-----Panel and regime------------------------------------------------------//
showPanel = 1 //1 = anchor price / direction / swing % panel
showRegime = 1 //1 = trend tint behind the price
dashX = -270 //panel: horizontal offset of the label column
dashY = -20 //panel: vertical offset from the top edge
dashCol = 110 //panel: column width
//-----Palette---------------------------------------------------------------//
// The lime / fuchsia of the original are calibrated for a black TradingView
// chart. Both are pulled down in luminance so they stay readable on the light
// PRT background, keeping the hue.
bullR = 0
bullG = 170
bullB = 80
bearR = 190
bearG = 0
bearB = 170
neuR = 105
neuG = 110
neuB = 120
txtR = 35
txtG = 38
txtB = 45
//---------------------------------------------------------------------------//
//1. SWING STRUCTURE
//---------------------------------------------------------------------------//
// A new N-bar high turns the structure bullish, a new N-bar low turns it
// bearish. A swing is CONFIRMED when the state flips: the pivot is then placed
// retroactively on the opposite extreme, which is the one that has just been
// left behind.
//---------------------------------------------------------------------------//
ONCE hiIdx = 0
ONCE hiVal = high
ONCE loIdx = 0
ONCE loVal = low
ONCE hiSet = 0
ONCE loSet = 0
ONCE dirUp = 0
ONCE nMk = 0
ONCE swIdx = 0
ONCE swPrice = 0
ONCE swReady = 0
ONCE lockRng = 0
ONCE lockLen = 0
mkRing = maxMarks + 1 // one spare slot: it is written on the live bar
// state as it stood at the close of the previous bar
prevDir = dirUp
pHiIdx = hiIdx
pHiVal = hiVal
pLoIdx = loIdx
pLoVal = loVal
pHiSet = hiSet
pLoSet = loSet
IF barindex >= swingLength - 1 THEN
hiWin = highest[swingLength](high)
loWin = lowest[swingLength](low)
IF high = hiWin THEN
hiIdx = barindex
hiVal = high
hiSet = 1
dirUp = 1
ENDIF
// the low test runs AFTER the high test, as in the original: on a bar that
// is both the N-bar high and the N-bar low, the structure ends up bearish
IF low = loWin THEN
loIdx = barindex
loVal = low
loSet = 1
dirUp = 0
ENDIF
IF dirUp <> prevDir THEN
// range and leg length are FROZEN here and drive the size of the spiral
// until the next flip
rngLen = min(500, barindex + 1)
lockRng = highest[rngLen](high) - lowest[rngLen](low)
IF dirUp = 1 THEN
lockLen = barindex - loIdx + 20
IF loSet = 1 THEN
swIdx = loIdx
swPrice = loVal
swReady = 1
ENDIF
ELSE
lockLen = barindex - hiIdx + 20
IF hiSet = 1 THEN
swIdx = hiIdx
swPrice = hiVal
swReady = 1
ENDIF
ENDIF
// Leg of the zigzag: from the opposite extreme AS IT STOOD on the
// previous bar to the pivot just confirmed. Without both extremes really
// recorded the leg would start on bar 0 at the price of bar 0, which
// draws a huge segment and stretches the price axis.
IF pHiSet = 1 AND pLoSet = 1 THEN
slot = nMk MOD mkRing
IF dirUp = 1 THEN
$mkXa[slot] = pHiIdx
$mkYa[slot] = pHiVal
$mkXb[slot] = loIdx
$mkYb[slot] = loVal
ELSE
$mkXa[slot] = pLoIdx
$mkYa[slot] = pLoVal
$mkXb[slot] = hiIdx
$mkYb[slot] = hiVal
ENDIF
$mkD[slot] = dirUp
nMk = nMk + 1
ENDIF
ENDIF
ENDIF
//---------------------------------------------------------------------------//
//2. TREND TINT
//---------------------------------------------------------------------------//
// Replaces the row of coloured squares the original prints at the bottom of
// the pane: ProBuilder has no location.bottom, and any fixed price level would
// drift with the zoom.
//---------------------------------------------------------------------------//
bgA = 0
IF showRegime = 1 THEN
bgA = 14
ENDIF
IF dirUp = 1 THEN
BACKGROUNDCOLOR(bullR, bullG, bullB, bgA)
ELSE
BACKGROUNDCOLOR(bearR, bearG, bearB, bgA)
ENDIF
//---------------------------------------------------------------------------//
//3. DRAWING
//---------------------------------------------------------------------------//
// Everything is repainted from stored state inside a single islastbarupdate
// block, so the defparam above can only help: the spiral and the panel are not
// stacked bar after bar, and the swing marks are not lost because they are
// replayed from the ring buffer instead of being drawn on their own bar.
//---------------------------------------------------------------------------//
IF islastbarupdate AND swReady = 1 THEN
//-----3.1 Zigzag of confirmed swings-------------------------------------//
nShow = min(nMk, maxMarks)
stMk = (nMk - nShow) MOD mkRing
FOR k = 0 TO nShow - 1 DO
sl = (stMk + k) MOD mkRing
IF $mkD[sl] = 1 THEN
// bullish flip: the leg that just ended is the bearish one, and the
// pivot marked is a low
segR = bearR
segG = bearG
segB = bearB
ptR = bullR
ptG = bullG
ptB = bullB
ELSE
segR = bullR
segG = bullG
segB = bullB
ptR = bearR
ptG = bearG
ptB = bearB
ENDIF
DRAWSEGMENT($mkXa[sl], $mkYa[sl], $mkXb[sl], $mkYb[sl]) COLOURED(segR, segG, segB, 255) STYLE(line, 1)
DRAWPOINT($mkXb[sl], $mkYb[sl], 3) COLOURED(ptR, ptG, ptB, 255)
NEXT
//-----3.2 Spiral---------------------------------------------------------//
resN = max(3, spiralRes)
// Pine builds it in radians over two arrays. Solving the algebra first
// removes both: the number of arms cancels out on each axis, so
//
// x(u) = anchorBar - u * cos(theta) * radius * lockLen
// y(u) = anchorPrice + s * u * sin(theta) * lockRange * radius / dY
//
// with u from 0 to 1, theta from 0 to arms * 180 degrees, s = -1 when the
// structure is bullish, and dY the peak-to-peak of u * sin(theta), which
// only the first pass can know. Consequence worth reading twice: the total
// height of the spiral is exactly lockRange * radius, so radius is not a
// cosmetic size, it multiplies the 500-bar range.
//
// The angle advances by rotation recurrence instead of evaluating
// sin/cos of a number of degrees that reaches 1800 with 10 arms.
dAng = spiralArms * 180 / (resN - 1)
cD = cos(dAng)
sD = sin(dAng)
// pass 1: peak-to-peak of u * sin(theta)
cA = 1
sA = 0
yLo = 0
yHi = 0
FOR i = 0 TO resN - 1 DO
yv = i / (resN - 1) * sA
IF yv < yLo THEN
yLo = yv
ENDIF
IF yv > yHi THEN
yHi = yv
ENDIF
cN = cA * cD - sA * sD
sN = sA * cD + cA * sD
cA = cN
sA = sN
NEXT
dY = yHi - yLo
IF dY > 0 THEN
yScale = lockRng * spiralRad / dY
IF dirUp = 1 THEN
dirSign = -1
ELSE
dirSign = 1
ENDIF
// pass 2: draw
cA = 1
sA = 0
xPrev = 0
yPrev = 0
FOR i = 0 TO resN - 1 DO
u = i / (resN - 1)
xCur = round(swIdx - u * cA * spiralRad * lockLen)
yCur = swPrice + dirSign * u * sA * yScale
IF i > 0 THEN
// the original hides whatever falls more than 500 bars to the
// right of the last bar, which is where Pine stops drawing
IF xPrev >= 0 AND xCur >= 0 AND xPrev - barindex < maxFwd THEN
norm = (i - 1) / (resN - 2)
gR = round(bearR + (bullR - bearR) * norm)
gG = round(bearG + (bullG - bearG) * norm)
gB = round(bearB + (bullB - bearB) * norm)
DRAWSEGMENT(xPrev, yPrev, xCur, yCur) COLOURED(gR, gG, gB, 255) STYLE(line, 2)
ENDIF
ENDIF
xPrev = xCur
yPrev = yCur
cN = cA * cD - sA * sD
sN = sA * cD + cA * sD
cA = cN
sA = sN
NEXT
// settings tag on the outer end of the spiral
IF xPrev >= 0 AND xPrev - barindex < maxFwd THEN
DRAWTEXT("Spirals: #spiralArms#", xPrev, yPrev, sansserif, standard, 10) COLOURED(txtR, txtG, txtB, 255)
DRAWTEXT("Radius: #spiralRad#", xPrev, yPrev - lockRng * spiralRad / 22, sansserif, standard, 10) COLOURED(txtR, txtG, txtB, 255)
ENDIF
ENDIF
//-----3.3 Live leg and panel---------------------------------------------//
IF dirUp = 1 THEN
actPx = high
ELSE
actPx = low
ENDIF
DRAWSEGMENT(swIdx, swPrice, barindex, actPx) COLOURED(neuR, neuG, neuB, 255) STYLE(dottedline, 1)
IF showPanel = 1 THEN
movePct = round((actPx - swPrice) / swPrice * 10000) / 100
// decimals from the scale of the instrument: format.mintick has no
// equivalent, and a fixed round(x, 2) flattens forex
IF swPrice >= 100 THEN
shownPx = round(swPrice, 2)
ELSIF swPrice >= 1 THEN
shownPx = round(swPrice, 4)
ELSE
shownPx = round(swPrice, 6)
ENDIF
// DRAWTEXT centres the string on its anchor point, so the frame has to
// clear half a column on each side
halfCol = dashCol / 2
boxL = dashX - halfCol - 10
boxR = dashX + dashCol + halfCol + 10
cx1 = dashX
cx2 = dashX + dashCol
DRAWRECTANGLE(boxL, dashY + 12, boxR, dashY - 82) ANCHOR(topright, xshift, yshift) COLOURED(neuR, neuG, neuB, 200) FILLCOLOR(250, 250, 250, 190)
DRAWTEXT("Anchor Price", cx1, dashY - 12, sansserif, standard, 11) ANCHOR(topright, xshift, yshift) COLOURED(neuR, neuG, neuB, 255)
DRAWTEXT("#shownPx#", cx2, dashY - 12, sansserif, bold, 11) ANCHOR(topright, xshift, yshift) COLOURED(txtR, txtG, txtB, 255)
DRAWTEXT("Direction", cx1, dashY - 37, sansserif, standard, 11) ANCHOR(topright, xshift, yshift) COLOURED(neuR, neuG, neuB, 255)
IF dirUp = 1 THEN
DRAWTEXT("BULLISH ▲", cx2, dashY - 37, sansserif, bold, 11) ANCHOR(topright, xshift, yshift) COLOURED(bullR, bullG, bullB, 255)
ELSE
DRAWTEXT("BEARISH ▼", cx2, dashY - 37, sansserif, bold, 11) ANCHOR(topright, xshift, yshift) COLOURED(bearR, bearG, bearB, 255)
ENDIF
DRAWTEXT("Swing %", cx1, dashY - 62, sansserif, standard, 11) ANCHOR(topright, xshift, yshift) COLOURED(neuR, neuG, neuB, 255)
IF movePct >= 0 THEN
DRAWTEXT("#movePct#%", cx2, dashY - 62, sansserif, bold, 11) ANCHOR(topright, xshift, yshift) COLOURED(bullR, bullG, bullB, 255)
ELSE
DRAWTEXT("#movePct#%", cx2, dashY - 62, sansserif, bold, 11) ANCHOR(topright, xshift, yshift) COLOURED(bearR, bearG, bearB, 255)
ENDIF
ENDIF
ENDIF
RETURN