Starry Night — Pressure Sky transforms volatility, price pressure, and range breakouts into a painted night landscape inspired by Vincent van Gogh. Cobalt brushstrokes, curved skies, golden lights, and village silhouettes surround a market reading built entirely from price data.
The indicator follows three conditions: compression, directional pressure, and a breakout following compression.
Compression occurs when the average true range of the latest six candles falls below 72% of an earlier 40-candle average. That earlier baseline excludes the six candles being evaluated. This identifies reduced price movement relative to the market’s own recent history. The periods and threshold are adjustable.
The pressure score ranges from −100 to +100. It combines the closing price’s position within the preceding 24-bar range with how directly price has moved over that period. Positive readings indicate upward pressure; negative readings indicate downward pressure. Scores above +45 or below −45 receive an upward or downward pressure label. The score is not a probability.
The thin gold and blue line displays smoothed pressure. Small golden stars mark qualifying upside breakouts; small ice-blue stars mark qualifying downside breakouts. A breakout requires a close beyond the preceding range and an adjustable buffer, with compression detected within the preceding 12 candles. These stars confirm a range break that has already occurred.
The main-chart readout displays the pressure score, volatility ratio, and market state:
- Compression: recent volatility is below the configured threshold.
- Release watch: compression occurred recently, but the current reading is no longer compressed and has no fresh qualifying breakout.
- Upside escape / Downside escape: a qualifying breakout has occurred.
- Open sky: none of those conditions currently applies.
The larger celestial halos, regularly placed swirls, cypress trees, and village are decorative. Their locations do not identify trading opportunities. Some brushwork responds to the readings, but the readout, pressure line, and small breakout stars carry the explicit signals.
Six conditions are available for scanners, alerts, and strategy testing: Compression, Upside pressure setup, Downside pressure setup, Golden star: upside escape, Ice star: downside escape, and Volatility expansion.
Completed-candle mode is enabled by default, displaying each reading on the following bar. The calculations use no future candles. Live mode allows readings to change while a candle forms, and decorative patterns may shift when earlier history is loaded.
Starry Night is designed to help distinguish contracting volatility, directional pressure, and subsequent range breaks. Its predictive performance has not been established, and a breakout signal does not guarantee continuation.
describe_indicator('Starry Night - Pressure Sky', 'lower', {
shortName: 'Starry Sky',
decimals: 1,
mainColorInheritFrom: 'Starlight current'
});
// Compression, range pressure, and breakout detection.
// No future bars are used.
// Completed-candle mode displays readings one bar later.
// Pressure is a heuristic score, not a probability.
// Visual smoothing affects artwork only.
// Decorative patterns can shift when earlier history is loaded.
// ───────────────────── INPUTS ─────────────────────
const snData = input.group('Market structure');
const snWindow = snData.number(
'Range lookback', 24, { min: 5, max: 150 }
);
const snShort = snData.number(
'Compression window', 6, { min: 2, max: 30 }
);
const snBase = snData.number(
'Volatility baseline', 40, { min: 10, max: 200 }
);
const snCutoff = snData.number(
'Compression ratio below', 0.72,
{ min: 0.2, max: 0.95 }
);
const snMemory = snData.number(
'Compression memory', 12, { min: 2, max: 50 }
);
const snBuffer = snData.number(
'Breakout buffer', 0.1,
{ min: 0, max: 1 }
);
const snClosed = snData.boolean(
'Use completed candles', true
);
const snArt = input.group('Painting');
const snBrush = snArt.number(
'Swirl length in bars', 90, { min: 30, max: 200 }
);
const snStroke = snArt.number(
'Brush thickness', 2, { min: 1, max: 4 }
);
const snScenery = snArt.boolean(
'Village and cypress', true
);
const snMoonOn = snArt.boolean(
'Decorative moon', true
);
const snStarsOn = snArt.boolean(
'Breakout stars', true
);
const snCardOn = snArt.boolean(
'Main chart readout', true
);
assert(
snBase > snShort,
'Volatility baseline must exceed compression window.'
);
assert(
close.length >
Math.max(snWindow, snBase + snShort) + snMemory + 3,
'Load more chart history.'
);
// ───────────────────── HELPERS ─────────────────────
const snN = close.length;
const snOK = snV => Number.isFinite(snV);
const snClamp = (snV, snLo, snHi) =>
Math.max(snLo, Math.min(snHi, snV));
const snQuiet = {
hideInLegend: true,
editorHidden: true,
hideInScriptEditor: true
};
const snLine = (snName, snColor, snWidth) => ({
name: snName,
color: snColor,
thickness: snWidth || 1,
...snQuiet
});
const snDots = (snValues, snName, snColor, snSize) =>
paint(snValues, {
name: snName,
color: snColor,
style: 'dotted',
thickness: snSize,
...snQuiet
});
const snMean = (snValues, snEnd, snLength) => {
if (snEnd - snLength + 1 < 0) return null;
let snTotal = 0;
for (
let snJ = snEnd - snLength + 1;
snJ <= snEnd;
snJ += 1
) {
if (!snOK(snValues[snJ])) return null;
snTotal += snValues[snJ];
}
return snTotal / snLength;
};
const snSmooth = snValues => {
let snPrevious = null;
return snValues.map(snV => {
if (!snOK(snV)) return null;
snPrevious = snPrevious === null
? snV
: snPrevious + 0.5 * (snV - snPrevious);
return snPrevious;
});
};
// ───────────────────── MARKET MODEL ─────────────────────
// True range includes gaps from the previous close.
const snTR = close.map((snV, snI) => {
if (!snOK(high[snI]) || !snOK(low[snI])) {
return null;
}
return snI > 0 && snOK(close[snI - 1])
? Math.max(
high[snI] - low[snI],
Math.abs(high[snI] - close[snI - 1]),
Math.abs(low[snI] - close[snI - 1])
)
: high[snI] - low[snI];
});
const snRaw = close.map((snV, snI) => {
if (
snI < Math.max(snWindow, snBase + snShort) ||
!snOK(snV)
) {
return null;
}
const snRecent = snMean(snTR, snI, snShort);
// The baseline excludes the recent compression window.
const snNormal = snMean(
snTR,
snI - snShort,
snBase
);
if (
!snOK(snRecent) ||
!snOK(snNormal) ||
snNormal <= 0
) {
return null;
}
let snHigh = -Infinity;
let snLow = Infinity;
let snPath = 0;
// Range boundaries exclude the bar being evaluated.
for (
let snJ = snI - snWindow;
snJ < snI;
snJ += 1
) {
if (
!snOK(high[snJ]) ||
!snOK(low[snJ]) ||
!snOK(close[snJ]) ||
!snOK(close[snJ + 1])
) {
return null;
}
snHigh = Math.max(snHigh, high[snJ]);
snLow = Math.min(snLow, low[snJ]);
snPath += Math.abs(
close[snJ + 1] - close[snJ]
);
}
const snLocation = snHigh > snLow
? snClamp(
2 * (snV - snLow) / (snHigh - snLow) - 1,
-1,
1
)
: 0;
const snEfficiency = snPath > 0
? (snV - close[snI - snWindow]) / snPath
: 0;
return {
pressure: 0.65 * snLocation + 0.35 * snEfficiency,
ratio: snRecent / snNormal,
compressed: snRecent / snNormal < snCutoff,
above: snV > snHigh + snBuffer * snNormal,
below: snV < snLow - snBuffer * snNormal
};
});
// Breakout events require compression on an earlier bar.
const snRawUp = close.map(() => false);
const snRawDown = close.map(() => false);
const snRawWatch = close.map(() => false);
for (let snI = 1; snI < snN; snI += 1) {
if (!snRaw[snI] || !snRaw[snI - 1]) continue;
let snWasTight = false;
for (
let snJ = Math.max(0, snI - snMemory);
snJ < snI;
snJ += 1
) {
if (snRaw[snJ] && snRaw[snJ].compressed) {
snWasTight = true;
}
}
snRawWatch[snI] =
snWasTight || snRaw[snI].compressed;
snRawUp[snI] =
snWasTight &&
snRaw[snI].above &&
!snRaw[snI - 1].above;
snRawDown[snI] =
snWasTight &&
snRaw[snI].below &&
!snRaw[snI - 1].below;
}
// Shift readings forward one bar when completed-candle mode is on.
const snLag = snClosed ? 1 : 0;
const snReading = close.map((snV, snI) =>
snI >= snLag ? snRaw[snI - snLag] : null
);
const snReady = snReading.map(snR =>
snR !== null && snR !== undefined
);
const snPressure = snReading.map(snR =>
snR ? snR.pressure : null
);
const snRatio = snReading.map(snR =>
snR ? snR.ratio : null
);
const snTight = snReading.map(snR =>
!!snR && snR.compressed
);
const snWatch = close.map((snV, snI) =>
snI >= snLag && snRawWatch[snI - snLag]
);
const snUp = close.map((snV, snI) =>
snI >= snLag && snRawUp[snI - snLag]
);
const snDown = close.map((snV, snI) =>
snI >= snLag && snRawDown[snI - snLag]
);
// Smoothing below is for display only.
const snVisual = snSmooth(snPressure);
const snCenter = snVisual.map(snV =>
snOK(snV) ? 53 + 16 * snV : null
);
const snEnergy = snSmooth(snRatio).map(snV =>
snOK(snV)
? snClamp((snV - 0.45) / 1.15, 0, 1)
: 0
);
// ───────────────────── PAINTED SKY ─────────────────────
const snTop = paint(
horizontal_line(100),
snLine('Sky ceiling', '#101E3D')
);
const snBottom = paint(
horizontal_line(0),
snLine('Sky floor', '#101E3D')
);
fill(
snTop,
snBottom,
'#101E3D',
1,
'Cobalt canvas'
);
// Eddies repeat on a fixed bar grid.
// Their positions are decorative, not trading signals.
const snCycle = Math.round(snBrush * 2);
const snLocal = snIndex =>
(
(snIndex % snCycle) -
snCycle * 0.55
) / (snBrush * 0.29);
const snWhorlY = snIndex =>
65 +
3 * Math.sin(
Math.floor(snIndex / snCycle) * 2.1
);
const snPalette = [
'#254A72',
'#32618B',
'#497B98',
'#7FA5AC',
'#2B557F',
'#B2BCA0'
];
const snLayers = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21
];
// Short line segments with null gaps produce broken brushwork.
snLayers.forEach(snK => {
const snMarks = close.map((snValue, snIndex) => {
const snGap = 7 + snK % 4;
const snPosition = (snIndex + snK * 3) % snGap;
if (snPosition >= 3 + snK % 3) return null;
const snPhase =
2 * Math.PI * snIndex /
(snBrush * (1 + 0.025 * snK));
const snLift = snOK(snCenter[snIndex])
? (snCenter[snIndex] - 53) * 0.23
: 0;
const snY =
26 +
snK * 3.05 +
snLift +
(3 + 4 * snEnergy[snIndex]) *
Math.sin(snPhase + snK * 0.19) +
1.1 * Math.sin(
snIndex * 0.51 + snK * 2.3
);
const snX = snLocal(snIndex);
const snDY = (snY - snWhorlY(snIndex)) / 19;
// Leave space for the curled strokes.
if (snX * snX + snDY * snDY < 1.08) {
return null;
}
return snClamp(snY, 23, 97);
});
paint(
snMarks,
snLine(
'Oil strokes ' + snK,
snPalette[snK % 6],
snStroke
)
);
});
// Broken upper and lower arcs.
// Curl size responds to volatility; arc geometry is decorative.
[0, 1, 2, 3, 4, 5].forEach(snK => {
[-1, 1].forEach(snSide => {
const snArc = close.map((snValue, snIndex) => {
const snRadius =
(1 - snK * 0.135) *
(0.72 + 0.28 * snEnergy[snIndex]);
const snX = snLocal(snIndex) / snRadius;
if (Math.abs(snX) > 1) return null;
if (
(
snIndex +
snK * 2 +
(snSide > 0 ? 3 : 0)
) % 9 > 5
) {
return null;
}
const snAngle = Math.acos(
snClamp(snX, -1, 1)
);
return (
snWhorlY(snIndex) +
snSide *
18 *
snRadius *
Math.sqrt(
Math.max(0, 1 - snX * snX)
) +
1.5 * Math.sin(
snAngle * 3 + snK * 0.7
) +
snK * 0.35 * snX
);
});
paint(
snArc,
snLine(
'Whorl ' + snK + ' ' + snSide,
[
'#365F85',
'#70949F',
'#B9C4A5',
'#4B879F',
'#8BB0B5',
'#D2D0AA'
][snK],
snStroke + 0.5
)
);
});
});
// ───────────────────── PRESSURE TRACE ─────────────────────
// Gold: positive smoothed pressure.
// Blue: negative smoothed pressure.
// Neutral pressure sits at panel altitude 53.
paint(
snCenter.map(snValue =>
snOK(snValue) && snValue >= 53
? snValue
: null
),
{
name: 'Starlight current',
color: '#E8D490',
thickness: 1
}
);
paint(
snCenter.map(snValue =>
snOK(snValue) && snValue < 53
? snValue
: null
),
snLine('Blue pressure current', '#91C6DA')
);
// ───────────────────── BREAKOUT STARS ─────────────────────
// Stars are placed when the breakout becomes known.
const snEventY = snEvents =>
close.map((snValue, snIndex) =>
snStarsOn && snEvents[snIndex]
? snCenter[snIndex] + 7
: null
);
const snGoldStars = snEventY(snUp);
const snIceStars = snEventY(snDown);
snDots(
snGoldStars,
'Bullish star pigment',
'#A69A58',
5
);
snDots(
snGoldStars,
'Bullish star core',
'#FFF1AD',
2
);
snDots(
snIceStars,
'Bearish star pigment',
'#56839B',
5
);
snDots(
snIceStars,
'Bearish star core',
'#DDF3F4',
2
);
// ───────────────────── CELESTIAL BRUSHWORK ─────────────────────
// Decorative halos carry no signal.
// Horizontal radius is in bars; vertical radius is in panel units.
const snMoonCycle = snCycle * 2;
const snMoonX = snIndex =>
(
(snIndex % snMoonCycle) -
snMoonCycle * 0.84
) / (snBrush * 0.13);
[0, 1, 2].forEach(snK => {
[-1, 1].forEach(snSide => {
paint(
close.map((snValue, snIndex) => {
if (!snMoonOn) return null;
const snRadius = 1 - snK * 0.22;
const snX = snMoonX(snIndex) / snRadius;
if (
Math.abs(snX) > 1 ||
(
snIndex +
snK +
(snSide > 0 ? 2 : 0)
) % 7 > 4
) {
return null;
}
return (
86 +
snSide *
10 *
snRadius *
Math.sqrt(
Math.max(0, 1 - snX * snX)
) +
0.6 * Math.sin(
snIndex * 1.4 + snK
)
);
}),
snLine(
'Moon brush ' + snK + ' ' + snSide,
['#7F927C', '#C5BC77', '#F0D886'][snK],
snStroke + 1
)
);
});
});
snDots(
close.map((snValue, snIndex) =>
snMoonOn &&
snIndex % snMoonCycle ===
Math.round(snMoonCycle * 0.84)
? 86
: null
),
'Moon luminous core',
'#F8E7A4',
4
);
// ───────────────────── LANDSCAPE ─────────────────────
const snFar = paint(
close.map((snValue, snIndex) =>
19 +
3 * Math.sin(snIndex / 31) +
1.7 * Math.sin(snIndex / 11) +
0.5 * Math.sin(snIndex / 3)
),
snLine('Distant hills', '#294969')
);
fill(
snFar,
snBottom,
'#294969',
1,
'Far hillside'
);
const snNear = paint(
close.map((snValue, snIndex) =>
12 +
2 * Math.sin(snIndex / 23 + 1) +
1.2 * Math.sin(snIndex / 7)
),
snLine('Near hills', '#172E47')
);
fill(
snNear,
snBottom,
'#172E47',
1,
'Near hillside'
);
paint(
close.map((snValue, snIndex) =>
snIndex % 7 < 4
? 18 +
3 * Math.sin(snIndex / 31) +
1.7 * Math.sin(snIndex / 11)
: null
),
snLine('Hill brushwork', '#4D7184', 2)
);
const snRoof = paint(
close.map((snValue, snIndex) => {
if (!snScenery) return null;
const snCell = snIndex % 17;
const snHouse = Math.floor(snIndex / 17);
return (
4 +
2 * Math.sin(snHouse * 2.4) +
(
snCell < 12
? (4 + snHouse % 4) *
(1 - Math.abs(snCell - 6) / 7)
: 0
)
);
}),
snLine('Village roofs', '#0D1C2B')
);
fill(
snRoof,
snBottom,
'#0D1C2B',
1,
'Village silhouette'
);
snDots(
close.map((snValue, snIndex) =>
snScenery && snIndex % 17 === 6
? 3.5 +
1.3 * Math.sin(
Math.floor(snIndex / 17) * 2.4
)
: null
),
'Ochre windows',
'#C3AA67',
1
);
// Fixed repeating landscape motif.
// The cypress is not tied to a market event.
const snTreeY = (snIndex, snInset) => {
if (!snScenery) return null;
const snX =
(
(snIndex % snMoonCycle) -
snMoonCycle * 0.12
) /
(snBrush * (snInset ? 0.07 : 0.11));
if (Math.abs(snX) > 1) return null;
const snBody = Math.pow(
1 - Math.abs(snX),
0.7
);
return (
3 +
72 * snBody +
2 * Math.sin(snX * 21) * (1 - snBody)
);
};
const snTree = paint(
close.map((snValue, snIndex) =>
snTreeY(snIndex, false)
),
snLine('Cypress', '#0D211F')
);
fill(
snTree,
snBottom,
'#0D211F',
1,
'Cypress silhouette'
);
paint(
close.map((snValue, snIndex) =>
snIndex % 6 < 4
? snTreeY(snIndex, true)
: null
),
snLine('Cypress strokes', '#42513A', 2)
);
// ───────────────────── READOUT ─────────────────────
// Readout uses unsmoothed model values.
const snLast = snN - 1;
const snHas = snReady[snLast];
const snP = snHas ? snPressure[snLast] : 0;
const snDirection = !snHas
? 'Warming up'
: snP > 0.45
? 'Upward pressure'
: snP < -0.45
? 'Downward pressure'
: 'Mixed pressure';
const snState = !snHas
? 'Waiting for data'
: snUp[snLast]
? 'Upside escape'
: snDown[snLast]
? 'Downside escape'
: snTight[snLast]
? 'Compression'
: snWatch[snLast]
? 'Release watch'
: 'Open sky';
const snRows = snCardOn
? [
{
cells: [
{
colspan: 2,
text: 'STARRY NIGHT | PRESSURE SKY',
color: '#E7D899',
fontSize: '9px',
fontWeight: '600',
padding: '7px 10px',
background: '#1A2C47'
}
]
},
{
cells: [
{
text: snDirection,
color: snP < 0 ? '#AED1E1' : '#F0D98E',
fontSize: '10px',
padding: '6px 8px 3px 10px'
},
{
text: snHas
? 'P ' + (100 * snP).toFixed(0)
: '--',
color: '#DDE7EC',
fontSize: '10px',
padding: '6px 10px 3px 0px'
}
]
},
{
cells: [
{
colspan: 2,
text:
snState +
(
snHas
? ' | Vol ' +
snRatio[snLast].toFixed(2) +
'x'
: ''
),
color: '#A9BBCB',
fontSize: '9px',
padding: '2px 10px'
}
]
},
{
cells: [
{
colspan: 2,
text: snClosed
? 'Completed candle | Score, not probability'
: 'Live candle | Score, not probability',
color: '#8399AE',
fontSize: '8px',
padding: '2px 10px 7px 10px'
}
]
}
]
: [];
paint_overlay(
'Starry Night readout',
{
position: 'bottom_right',
parent: 'chart',
order: 'above_all',
offset_x: -120,
offset_y: -16
},
{
background: snCardOn ? '#0B1532' : 'transparent',
borderRadius: '8px',
border: snCardOn ? '1px solid #34435A' : 'none',
borderCollapse: 'separate',
borderSpacing: '0px',
fontFamily: 'Arial, sans-serif',
rows: snRows
}
);
// ───────────────────── SIGNALS ─────────────────────
// Raw model values, respecting completed-candle mode.
register_signal(
snTight,
'Compression'
);
register_signal(
close.map((snV, snI) =>
snReady[snI] &&
snWatch[snI] &&
snPressure[snI] > 0.45 &&
!snReading[snI].above
),
'Upside pressure setup'
);
register_signal(
close.map((snV, snI) =>
snReady[snI] &&
snWatch[snI] &&
snPressure[snI] < -0.45 &&
!snReading[snI].below
),
'Downside pressure setup'
);
register_signal(
snUp,
'Golden star: upside escape'
);
register_signal(
snDown,
'Ice star: downside escape'
);
register_signal(
close.map((snV, snI) =>
snI > 0 &&
snReady[snI] &&
snReady[snI - 1] &&
snRatio[snI] > 1.2 &&
snRatio[snI - 1] <= 1.2
),
'Volatility expansion'
);