Market Gravity
Сообщите нам, если считаете, что материал размещён без разрешения.
Сообщить о материале
Обращение по товару «indicator market gravity for tradingview.pine» отправится на рассмотрение. Укажите контакт для ответа и опишите причину.
О программе
Этот индикатор Pine Script v6 для TradingView анализирует инфирированные структуры ликвидности выше и ниже текущей цены. Он выявляет подтверждённые развороты, равные максимумы и минимумы, значимые изолированные свечи, уровни предыдущих дня и недели, а также повторяющиеся структуры отторжения, объединяя их в пулы ликвидности. Каждый пул получает структурный показатель силы ликвидности и динамический балл притяжения (Gravity), учитывающий расстояние, свежесть, рыночный режим и направление тренда. Индикатор визуализирует сильнейшие цели по обеим сторонам ценового графика, отображает направление гравитационных потоков кривыми, аннотации жизненного цикла пулов и сводную таблицу с текущим состоянием магнитов.
Настройки программы
| Настройка | Описание |
|---|---|
| pivotLeft | Pivot Left Bars — Количество баров слева от свечи для подтверждения верхнего или нижнего пивота при определении структур ликвидности. |
| pivotRight | Pivot Right Bars — Количество баров справа, необходимых для подтверждения разворота перед тем, как он будет учтён как структура ликвидности. |
| clusterATR | Pool Cluster Distance (ATR) — Максимальное расстояние в единицах ATR, при котором совместимые структуры ликвидности объединяются в один пул. |
| sweepBufferATR | Sweep Buffer (ATR) — Дополнительный буфер в ATR, на который цена должна превысить уровень ликвидности, чтобы тот считался пройденным (swept). |
| maxAgeBars | Maximum Liquidity Age — Максимальное количество баров с момента создания пула, после чего он удаляется из анализа. |
| maxStoredLevels | Maximum Stored Levels — Максимальное число сохраняемых уровней ликвидности на каждой стороне (верхней и нижней). |
| maxTargetDistanceATR | Maximum Target Distance (ATR) — Максимальное расстояние в ATR от текущей цены, на котором уровень может быть ранжирован как целевой. |
| useSwingLiquidity | Swing High / Low Liquidity — Включает использование подтверждённых пивотов как базовых структур ликвидности. |
| useEqualHighLow | Equal Highs / Lows — Включает обнаружение повторяющихся подтверждённых пивотов, формирующих равные максимумы или минимумы. |
| useMajorSwings | Major Isolated Swings — Позволяет необычно значимым подтверждённым разворотам квалифицироваться как самостоятельные структуры ликвидности. |
| usePreviousDay | Previous Day High / Low — Использует предыдущий завершённый дневной максимум и минимум при таймфрейме дневном и ниже. |
| usePreviousWeek | Previous Week High / Low — Использует предыдущий завершённый недельный максимум и минимум при таймфрейме недельном и ниже. |
| useRepeatedRejections | Repeated Rejection Structures — Классифицирует повторяющиеся реакции цены вблизи одного уровня как структуры отторжения. |
| equalToleranceATR | Equal High / Low Tolerance (ATR) — Допуск в ATR для определения равенства двух уровней высоких или низких цен. |
| majorSwingATR | Major Swing Threshold (ATR) — Порог в ATR, превышение которого делает свинг значимым (major) для отдельной классификации. |
| rejectionMinSeparationBars | Rejection Minimum Separation — Минимальное число баров между реакциями, необходимое для квалификации как повторяющейся структуры отторжения. |
| rejectionWickRatio | Rejection Wick Ratio — Минимальная доля теневой свечи диапазона свечи, используемая при классификации структур отторжения. |
| recentPivotMemory | Recent Pivot Memory — Число недавних подтверждённых пивотов, сохраняемых для классификации структур ликвидности. |
| independentTouchMinSeparationBars | Independent Touch Minimum Separation — Минимальное число баров между событиями реакции цены, чтобы они считались отдельными касаниями пула. |
| clusterZonePaddingATR | Clustered Pool Zone Padding (ATR) — Отступ в ATR за пределы минимальной и максимальной цен членов сгруппированного пула. |
| isolatedZoneHalfATR | Isolated Pool Half Width (ATR) — Полуширина зоны для пула с одним членом до присоединения дополнительных структур. |
| maxZoneWidthATR | Maximum Pool Zone Width (ATR) — Ограничение ширины зоны пула, предотвращающее чрезмерно широкую кластеризацию. |
| atrLength | ATR Length — Период расчёта Average True Range, используемый для нормализации расстояний и допусков. |
| fastEMALength | Fast EMA Length — Длина быстрой экспоненциальной скользящей средней, используемой для определения направления тренда. |
| slowEMALength | Slow EMA Length — Длина медленной экспоненциальной скользящей средней, используемой для определения направления тренда. |
| volumeLength | Relative Volume Length — Период расчёта среднего объёма для определения относительного объёма свечи. |
| approachingATR | Approaching Distance (ATR) — Расстояние в ATR, при котором уровень считается приближающимся к текущей цене. |
| accessibilityKATR | Accessibility Distance K (ATR) — Коэффициент масштабирования нелинейного затухания доступности с расстоянием в единицах ATR. |
| swingFreshnessHalfLifeBars | Swing Freshness Half-Life (Bars) — Базовый период полураспада свежести для структур, основанных на свинг-пивотах. |
| clusterFreshnessHalfLifeBars | Cluster Freshness Half-Life (Bars) — Базовый период полураспада свежести для кластеризованных пулов с несколькими членами. |
| previousDayFreshnessHalfLifeBars | Previous Day Freshness Half-Life (Bars) — Базовый период полураспада свежести для структур предыдущего дня. |
| previousWeekFreshnessHalfLifeBars | Previous Week Freshness Half-Life (Bars) — Базовый период полураспада свежести для структур предыдущей недели. |
| activeLiquidityStrengthThreshold | Active Liquidity Strength Threshold — Порог структурной силы ликвидности, ниже которого пул остаётся в состоянии формирования и не участвует в ранжировании целей. |
| rejectionWindowBars | Sweep Rejection Window (Bars) — Число баров после подтверждённого прохождения, в течение которых закрытие обратно на исходной стороне зоны классифицирует прохождение как отклонённое. |
| acceptanceCloseCount | Acceptance Consecutive Closes — Число последовательных подтверждённых закрытий за пределами внешней границы зоны, необходимых для классификации пройденной ликвидности как принятой. |
| lifecycleEventDisplayBars | Resolved State Hold (Bars) — Число баров, в течение которых сохраняется визуальное отображение событий жизненного цикла после их разрешения. |
| showCurves | Show Gravity Paths — Включает отображение кривых направления гравитационного потока от текущей цены к целевым уровням. |
| showOpposingCurve | Show Opposing Gravity Path — Включает отображение противоположной кривой гравитационного потока на графике. |
| projectionBars | Projection Length — Число баров вперёд, на которое проецируются кривые гравитационного потока. |
| displayLevels | Levels Per Side — Количество уровней ликвидности, отображаемых на каждой стороне (верхней и нижней) графика. |
| lineHistoryBars | Level Line History — Максимальное число баров назад, в течение которых сохраняются линии уровней на графике. |
| showGlow | Level Glow — Включает свечение (glow) вокруг линий уровней ликвидности для лучшей визуальной заметности. |
| showTable | Market Gravity Table — Включает отображение сводной таблицы с текущими значениями гравитации, давления и состояниями уровней. |
| showLifecycleEvents | Show Lifecycle Events — Включает кратковременное отображение событий жизненного цикла ликвидности (прохождение, отклонение, принятие) на графике. |
| labelCollisionMode | Label Collision Handling — Режим автоматического разрешения визуальных коллизий между метками уровней и аннотациями на графике. |
| labelCollisionThresholdATR | Label Collision Threshold (ATR) — Порог вертикального расстояния в ATR, при котором метки считаются визуально перекрывающимися и получают отдельные горизонтальные полосы. |
| rankMarkerSpacingBars | Label Base Lane Spacing (Bars) — Базовое горизонтальное расстояние между полосами для аннотаций уровней в барах. |
| lifecycleLabelGapBars | Lifecycle Label Gap (Bars) — Дополнительный горизонтальный отступ правее всех активных полос целей перед началом аннотаций событий жизненного цикла. |
| tablePositionInput | Table Position — Позиция сводной таблицы на графике (верхний правый, нижний правый, верхний левый или нижний левый угол). |
| primaryGravityAlertLevel | High Primary Gravity Threshold — Порог значения гравитации, при превышении которого срабатывает оповещение о высоком основном магните. |
| gravityAlertLevel | Strong Net Gravity Threshold — Порог, при превышении которого срабатывают оповещения о сильном направленном рыночном притяжении. |
| bullColor | Bullish Gravity — Цвет, используемый для визуализации бычьего (восходящего) гравитационного притяжения. |
| bearColor | Bearish Gravity — Цвет, используемый для визуализации медвежьего (нисходящего) гравитационного притяжения. |
| neutralColor | Neutral Gravity — Цвет, используемый для отображения нейтрального состояния гравитации. |
Поиск по коду файла
indicator(
"Market Gravity",
overlay = true,
max_lines_count = 100,
max_labels_count = 100,
max_polylines_count = 10
)
max_bars_back(time, 3001)
pivotLeft = input.int(
5,
"Pivot Left Bars",
minval = 1,
group = "Advanced - Liquidity Detection",
tooltip = "Bars to the left used when confirming swing liquidity."
)
pivotRight = input.int(
5,
"Pivot Right Bars",
minval = 1,
group = "Advanced - Liquidity Detection",
tooltip = "Bars to the right required before a swing is confirmed."
)
clusterATR = input.float(
0.15,
"Pool Cluster Distance (ATR)",
minval = 0.01,
step = 0.01,
group = "Advanced - Liquidity Detection",
tooltip = "Compatible liquidity structures within this ATR distance are clustered into one liquidity pool."
)
sweepBufferATR = input.float(
0.05,
"Sweep Buffer (ATR)",
minval = 0.0,
step = 0.01,
group = "Advanced - Liquidity Detection",
tooltip = "Price must exceed a liquidity level by this ATR amount before the level is considered swept."
)
maxAgeBars = input.int(
1500,
"Maximum Liquidity Age",
minval = 100,
maxval = 5000,
group = "Advanced - Liquidity Detection"
)
maxStoredLevels = input.int(
40,
"Maximum Stored Levels",
minval = 10,
maxval = 100,
group = "Advanced - Liquidity Detection"
)
maxTargetDistanceATR = input.float(
12.0,
"Maximum Target Distance (ATR)",
minval = 1.0,
step = 0.5,
group = "Advanced - Liquidity Detection"
)
useSwingLiquidity = input.bool(
true,
"Swing High / Low Liquidity",
group = "Advanced - Liquidity Sources",
tooltip = "Use confirmed swing pivots as baseline inferred liquidity."
)
useEqualHighLow = input.bool(
true,
"Equal Highs / Lows",
group = "Advanced - Liquidity Sources",
tooltip = "Detect repeated confirmed pivots forming equal-high or equal-low liquidity."
)
useMajorSwings = input.bool(
true,
"Major Isolated Swings",
group = "Advanced - Liquidity Sources",
tooltip = "Allow unusually prominent confirmed swings to qualify as their own liquidity structures."
)
usePreviousDay = input.bool(
true,
"Previous Day High / Low",
group = "Advanced - Liquidity Sources",
tooltip = "Use the previous completed daily high and low when the chart timeframe is daily or lower."
)
usePreviousWeek = input.bool(
true,
"Previous Week High / Low",
group = "Advanced - Liquidity Sources",
tooltip = "Use the previous completed weekly high and low when the chart timeframe is weekly or lower."
)
useRepeatedRejections = input.bool(
true,
"Repeated Rejection Structures",
group = "Advanced - Liquidity Sources",
tooltip = "Classify repeated confirmed reactions near the same price as rejection-based liquidity structures."
)
equalToleranceATR = input.float(
0.15,
"Equal High / Low Tolerance (ATR)",
minval = 0.01,
step = 0.01,
group = "Advanced - Liquidity Sources"
)
majorSwingATR = input.float(
2.50,
"Major Swing Threshold (ATR)",
minval = 0.50,
step = 0.25,
group = "Advanced - Liquidity Sources"
)
rejectionMinSeparationBars = input.int(
8,
"Rejection Minimum Separation",
minval = 1,
group = "Advanced - Liquidity Sources",
tooltip = "Minimum bars between reactions before they can qualify as a repeated rejection structure."
)
rejectionWickRatio = input.float(
0.30,
"Rejection Wick Ratio",
minval = 0.0,
maxval = 1.0,
step = 0.05,
group = "Advanced - Liquidity Sources",
tooltip = "Minimum wick share of the pivot candle range used when classifying repeated rejection structures."
)
recentPivotMemory = input.int(
30,
"Recent Pivot Memory",
minval = 5,
maxval = 100,
group = "Advanced - Liquidity Sources",
tooltip = "Number of recent confirmed pivots retained only for structure classification."
)
independentTouchMinSeparationBars = input.int(
8,
"Independent Touch Minimum Separation",
minval = 1,
group = "Advanced - Pool Engine",
tooltip = "Minimum bars between price-reaction events before they count as separate liquidity touches. Structural confirmations can join a pool without inflating touch count."
)
clusterZonePaddingATR = input.float(
0.02,
"Clustered Pool Zone Padding (ATR)",
minval = 0.0,
step = 0.01,
group = "Advanced - Pool Engine",
tooltip = "Padding added outside the minimum and maximum member prices of a clustered pool."
)
isolatedZoneHalfATR = input.float(
0.05,
"Isolated Pool Half Width (ATR)",
minval = 0.01,
step = 0.01,
group = "Advanced - Pool Engine",
tooltip = "Half-width used for a one-member liquidity pool before additional structures join it."
)
maxZoneWidthATR = input.float(
0.50,
"Maximum Pool Zone Width (ATR)",
minval = 0.10,
step = 0.05,
group = "Advanced - Pool Engine",
tooltip = "Safety bound that prevents an unusually broad cluster from becoming an oversized target zone."
)
atrLength = input.int(
14,
"ATR Length",
minval = 1,
group = "Advanced - Gravity Model"
)
fastEMALength = input.int(
21,
"Fast EMA Length",
minval = 1,
group = "Advanced - Gravity Model"
)
slowEMALength = input.int(
55,
"Slow EMA Length",
minval = 2,
group = "Advanced - Gravity Model"
)
volumeLength = input.int(
50,
"Relative Volume Length",
minval = 5,
group = "Advanced - Gravity Model"
)
approachingATR = input.float(
0.75,
"Approaching Distance (ATR)",
minval = 0.1,
step = 0.05,
group = "Advanced - Gravity Model"
)
primaryRankWeight = 1.00
secondaryRankWeight = 0.65
tertiaryRankWeight = 0.40
accessibilityKATR = input.float(
2.0,
"Accessibility Distance K (ATR)",
minval = 0.25,
step = 0.25,
group = "Advanced - Gravity Model",
tooltip = "Controls nonlinear distance decay. Around K ATR, accessibility is approximately 50%."
)
swingFreshnessHalfLifeBars = input.int(
150,
"Swing Freshness Half-Life (Bars)",
minval = 10,
group = "Advanced - Gravity Model"
)
clusterFreshnessHalfLifeBars = input.int(
250,
"Cluster Freshness Half-Life (Bars)",
minval = 10,
group = "Advanced - Gravity Model"
)
previousDayFreshnessHalfLifeBars = input.int(
300,
"Previous Day Freshness Half-Life (Bars)",
minval = 10,
group = "Advanced - Gravity Model"
)
previousWeekFreshnessHalfLifeBars = input.int(
500,
"Previous Week Freshness Half-Life (Bars)",
minval = 10,
group = "Advanced - Gravity Model"
)
activeLiquidityStrengthThreshold = input.float(
40.0,
"Active Liquidity Strength Threshold",
minval = 0.0,
maxval = 100.0,
step = 1.0,
group = "Advanced - Liquidity Lifecycle",
tooltip = "Pools below this structural Liquidity Strength remain Forming and are not eligible for target ranking."
)
rejectionWindowBars = input.int(
2,
"Sweep Rejection Window (Bars)",
minval = 1,
maxval = 10,
group = "Advanced - Liquidity Lifecycle",
tooltip = "After a confirmed sweep, a close back on the original side of the full zone within this many bars classifies the sweep as Rejected."
)
acceptanceCloseCount = input.int(
2,
"Acceptance Consecutive Closes",
minval = 1,
maxval = 5,
group = "Advanced - Liquidity Lifecycle",
tooltip = "Consecutive confirmed closes beyond the outer zone boundary required to classify swept liquidity as Accepted."
)
lifecycleEventDisplayBars = input.int(
3,
"Resolved State Hold (Bars)",
minval = 1,
maxval = 20,
group = "Advanced - Liquidity Lifecycle",
tooltip = "Bars to retain Rejected or Accepted state internally before the pool transitions to Retired and its lifecycle event visualization fades."
)
showCurves = input.bool(
true,
"Show Gravity Paths",
group = "Display - Projection"
)
showOpposingCurve = input.bool(
true,
"Show Opposing Gravity Path",
group = "Display - Projection"
)
projectionBars = input.int(
35,
"Projection Length",
minval = 10,
maxval = 100,
group = "Display - Projection"
)
displayLevels = input.int(
3,
"Levels Per Side",
minval = 1,
maxval = 3,
group = "Display"
)
lineHistoryBars = input.int(
500,
"Level Line History",
minval = 50,
maxval = 3000,
group = "Display"
)
showGlow = input.bool(
true,
"Level Glow",
group = "Display"
)
showTable = input.bool(
true,
"Market Gravity Table",
group = "Display"
)
showLifecycleEvents = input.bool(
true,
"Show Lifecycle Events",
group = "Display",
tooltip = "Briefly preserves Sweeping, Rejected, and Accepted liquidity events on the chart after they leave future-target ranking."
)
labelCollisionMode = input.string(
"Auto",
"Label Collision Handling",
options = [
"Auto",
"Off"
],
group = "Display",
tooltip = "Auto keeps target labels readable with horizontal lanes and repositions Gravity path badges only when they would overlap curved paths, liquidity lines, or another path badge. Target prices and curve geometry never move."
)
labelCollisionThresholdATR = input.float(
0.60,
"Label Collision Threshold (ATR)",
minval = 0.05,
maxval = 2.00,
step = 0.05,
group = "Display",
tooltip = "Targets within this ATR-normalized vertical distance are treated as a visual collision and assigned separate horizontal lanes."
)
rankMarkerSpacingBars = input.int(
18,
"Label Base Lane Spacing (Bars)",
minval = 1,
maxval = 40,
group = "Display",
tooltip = "Base horizontal spacing for collision lanes. Progressively larger X offsets are applied to later collision lanes so wide labels remain readable. True target prices never move vertically."
)
lifecycleLabelGapBars = input.int(
18,
"Lifecycle Label Gap (Bars)",
minval = 0,
maxval = 40,
group = "Display",
tooltip = "Extra horizontal gap beyond the furthest active target lane before lifecycle-event labels begin, so Accepted, Rejected, and Sweeping annotations remain readable."
)
tablePositionInput = input.string(
"Top Right",
"Table Position",
options = [
"Top Right",
"Bottom Right",
"Top Left",
"Bottom Left"
],
group = "Display"
)
primaryGravityAlertLevel = input.float(
70.0,
"High Primary Gravity Threshold",
minval = 1.0,
maxval = 100.0,
step = 1.0,
group = "Alerts",
tooltip = "Triggers the High Primary Gravity alert when the current upside or downside Primary Magnet crosses above this Gravity score. Gravity is an attraction score, not a probability."
)
gravityAlertLevel = input.float(
60.0,
"Strong Net Gravity Threshold",
minval = 1.0,
maxval = 100.0,
step = 1.0,
group = "Alerts",
tooltip = "Triggers strong directional Market Gravity alerts when Net Gravity crosses above +threshold or below -threshold. Net Gravity measures directional imbalance, not probability."
)
bullColor = input.color(
#00E5A8,
"Bullish Gravity",
group = "Style"
)
bearColor = input.color(
#FF4664,
"Bearish Gravity",
group = "Style"
)
neutralColor = input.color(
#A8B2C1,
"Neutral Gravity",
group = "Style"
)
pathColor = input.color(
#00C8FF,
"Primary Gravity Path",
group = "Style"
)
atr = ta.atr(atrLength)
fastEMA = ta.ema(close, fastEMALength)
slowEMA = ta.ema(close, slowEMALength)
volumeAverage = ta.sma(volume, volumeLength)
pivotHigh = ta.pivothigh(
high,
pivotLeft,
pivotRight
)
pivotLow = ta.pivotlow(
low,
pivotLeft,
pivotRight
)
pivotWindow = pivotLeft + pivotRight + 1
[previousDayHigh, previousDayLow] = request.security(
syminfo.tickerid,
"1D",
[high[1], low[1]],
lookahead = barmerge.lookahead_on
)
[previousWeekHigh, previousWeekLow] = request.security(
syminfo.tickerid,
"1W",
[high[1], low[1]],
lookahead = barmerge.lookahead_on
)
chartTimeframeSeconds = timeframe.in_seconds()
canUsePreviousDay = chartTimeframeSeconds <= timeframe.in_seconds("1D")
canUsePreviousWeek = chartTimeframeSeconds <= timeframe.in_seconds("1W")
newDay = timeframe.change("1D")
newWeek = timeframe.change("1W")
LIFECYCLE_FORMING = 0
LIFECYCLE_ACTIVE = 1
LIFECYCLE_APPROACHING = 2
LIFECYCLE_SWEEPING = 3
LIFECYCLE_REJECTED = 4
LIFECYCLE_ACCEPTED = 5
LIFECYCLE_RETIRED = 6
tablePosition =
tablePositionInput == "Top Right" ? position.top_right :
tablePositionInput == "Top Left" ? position.top_left :
tablePositionInput == "Bottom Left" ? position.bottom_left :
position.bottom_right
var array<float> upperPrices = array.new_float()
var array<int> upperTouches = array.new_int()
var array<int> upperBars = array.new_int()
var array<int> upperActiveBars = array.new_int()
var array<float> upperStrengths = array.new_float()
var array<float> upperVolumes = array.new_float()
var array<string> upperSources = array.new_string()
var array<float> upperZoneLows = array.new_float()
var array<float> upperZoneHighs = array.new_float()
var array<float> upperMemberMins = array.new_float()
var array<float> upperMemberMaxs = array.new_float()
var array<int> upperMemberCounts = array.new_int()
var array<float> upperTotalWeights = array.new_float()
var array<int> upperLastTouchBars = array.new_int()
var array<float> upperPoolATRs = array.new_float()
var array<float> upperMaxSwingProminences = array.new_float()
var array<int> upperSwingEvidenceCounts = array.new_int()
var array<float> upperVolumeEvidenceSums = array.new_float()
var array<int> upperVolumeEvidenceCounts = array.new_int()
var array<int> upperLifecycleStates = array.new_int()
var array<int> upperSweepBars = array.new_int()
var array<int> upperBeyondCloseCounts = array.new_int()
var array<int> upperResolutionBars = array.new_int()
var array<int> upperLifecycleTouchBars = array.new_int()
var array<float> lowerPrices = array.new_float()
var array<int> lowerTouches = array.new_int()
var array<int> lowerBars = array.new_int()
var array<int> lowerActiveBars = array.new_int()
var array<float> lowerStrengths = array.new_float()
var array<float> lowerVolumes = array.new_float()
var array<string> lowerSources = array.new_string()
var array<float> lowerZoneLows = array.new_float()
var array<float> lowerZoneHighs = array.new_float()
var array<float> lowerMemberMins = array.new_float()
var array<float> lowerMemberMaxs = array.new_float()
var array<int> lowerMemberCounts = array.new_int()
var array<float> lowerTotalWeights = array.new_float()
var array<int> lowerLastTouchBars = array.new_int()
var array<float> lowerPoolATRs = array.new_float()
var array<float> lowerMaxSwingProminences = array.new_float()
var array<int> lowerSwingEvidenceCounts = array.new_int()
var array<float> lowerVolumeEvidenceSums = array.new_float()
var array<int> lowerVolumeEvidenceCounts = array.new_int()
var array<int> lowerLifecycleStates = array.new_int()
var array<int> lowerSweepBars = array.new_int()
var array<int> lowerBeyondCloseCounts = array.new_int()
var array<int> lowerResolutionBars = array.new_int()
var array<int> lowerLifecycleTouchBars = array.new_int()
var array<float> recentUpperPivotPrices = array.new_float()
var array<int> recentUpperPivotBars = array.new_int()
var array<float> recentLowerPivotPrices = array.new_float()
var array<int> recentLowerPivotBars = array.new_int()
var array<line> displayLines = array.new_line()
var array<label> displayLabels = array.new_label()
var polyline upperPath = na
var polyline lowerPath = na
var string lastLifecycleEvent = "None"
var int lastLifecycleEventBar = -1
var table gravityTable = table.new(
tablePosition,
4,
9,
bgcolor = color.new(color.black, 15),
frame_color = color.new(color.gray, 75),
frame_width = 1,
border_color = color.new(color.gray, 90),
border_width = 1
)
clamp01(float value) =>
math.max(
0.0,
math.min(1.0, value)
)
clampSigned(float value) =>
math.max(
-1.0,
math.min(1.0, value)
)
formatPrice(float value) =>
na(value) ?
"-" :
str.tostring(
value,
format.mintick
)
formatScore(float value) =>
na(value) ?
"-" :
str.tostring(
int(math.round(value))
)
appendSource(
string currentSources,
string newSource
) =>
wrappedSources =
" + " +
currentSources +
" + "
wrappedNewSource =
" + " +
newSource +
" + "
currentSources == "" ?
newSource :
str.contains(
wrappedSources,
wrappedNewSource
) ?
currentSources :
currentSources + " + " + newSource
getRecentMatchCount(
array<float> prices,
array<int> bars,
float newPrice,
int newBar,
float tolerance,
int minimumSeparation
) =>
int matches = 0
if array.size(prices) > 0
for i = 0 to array.size(prices) - 1
oldPrice = array.get(
prices,
i
)
oldBar = array.get(
bars,
i
)
farEnough =
math.abs(
newBar -
oldBar
) >=
minimumSeparation
closeEnough =
math.abs(
newPrice -
oldPrice
) <=
tolerance
if farEnough and closeEnough
matches += 1
matches
storeRecentPivot(
array<float> prices,
array<int> bars,
float price,
int pivotBar,
int maximumSize
) =>
array.push(
prices,
price
)
array.push(
bars,
pivotBar
)
while array.size(prices) > maximumSize
array.shift(prices)
array.shift(bars)
getPoolZoneBounds(
float memberMin,
float memberMax,
int memberCount,
float centroid,
float poolATR
) =>
float zoneLow = na
float zoneHigh = na
if poolATR > 0
if memberCount <= 1
zoneLow := centroid - poolATR * isolatedZoneHalfATR
zoneHigh := centroid + poolATR * isolatedZoneHalfATR
else
zoneLow := memberMin - poolATR * clusterZonePaddingATR
zoneHigh := memberMax + poolATR * clusterZonePaddingATR
maximumWidth = poolATR * maxZoneWidthATR
currentWidth = zoneHigh - zoneLow
if maximumWidth > 0 and currentWidth > maximumWidth
zoneMidpoint = (zoneHigh + zoneLow) / 2.0
zoneLow := zoneMidpoint - maximumWidth / 2.0
zoneHigh := zoneMidpoint + maximumWidth / 2.0
[zoneLow, zoneHigh]
removeLevel(
array<float> prices,
array<int> touches,
array<int> bars,
array<int> activeBars,
array<float> strengths,
array<float> volumes,
array<string> sources,
array<float> zoneLows,
array<float> zoneHighs,
array<float> memberMins,
array<float> memberMaxs,
array<int> memberCounts,
array<float> totalWeights,
array<int> lastTouchBars,
array<float> poolATRs,
array<float> maxSwingProminences,
array<int> swingEvidenceCounts,
array<float> volumeEvidenceSums,
array<int> volumeEvidenceCounts,
array<int> lifecycleStates,
array<int> sweepBars,
array<int> beyondCloseCounts,
array<int> resolutionBars,
array<int> lifecycleTouchBars,
int index
) =>
array.remove(prices, index)
array.remove(touches, index)
array.remove(bars, index)
array.remove(activeBars, index)
array.remove(strengths, index)
array.remove(volumes, index)
array.remove(sources, index)
array.remove(zoneLows, index)
array.remove(zoneHighs, index)
array.remove(memberMins, index)
array.remove(memberMaxs, index)
array.remove(memberCounts, index)
array.remove(totalWeights, index)
array.remove(lastTouchBars, index)
array.remove(poolATRs, index)
array.remove(maxSwingProminences, index)
array.remove(swingEvidenceCounts, index)
array.remove(volumeEvidenceSums, index)
array.remove(volumeEvidenceCounts, index)
array.remove(lifecycleStates, index)
array.remove(sweepBars, index)
array.remove(beyondCloseCounts, index)
array.remove(resolutionBars, index)
array.remove(lifecycleTouchBars, index)
addPoolMember(
array<float> prices,
array<int> touches,
array<int> bars,
array<int> activeBars,
array<float> strengths,
array<float> volumes,
array<string> sources,
array<float> zoneLows,
array<float> zoneHighs,
array<float> memberMins,
array<float> memberMaxs,
array<int> memberCounts,
array<float> totalWeights,
array<int> lastTouchBars,
array<float> poolATRs,
array<float> maxSwingProminences,
array<int> swingEvidenceCounts,
array<float> volumeEvidenceSums,
array<int> volumeEvidenceCounts,
array<int> lifecycleStates,
array<int> sweepBars,
array<int> beyondCloseCounts,
array<int> resolutionBars,
array<int> lifecycleTouchBars,
float newPrice,
int newBar,
int newActiveBar,
float newStrength,
float newVolume,
float newStructuralSwingProminence,
float newStructuralVolumeScore,
string newSource,
float newWeight,
float newATR,
bool countsAsTouch,
int touchBar,
float tolerance,
int maximumLevels
) =>
int nearestIndex = -1
float nearestDistance = na
if array.size(prices) > 0
for i = 0 to array.size(prices) - 1
poolState = array.get(lifecycleStates, i)
canMerge =
poolState == LIFECYCLE_FORMING or
poolState == LIFECYCLE_ACTIVE or
poolState == LIFECYCLE_APPROACHING
if canMerge
poolCentroid = array.get(prices, i)
distance = math.abs(poolCentroid - newPrice)
if distance <= tolerance and (
na(nearestDistance) or
distance < nearestDistance
)
nearestIndex := i
nearestDistance := distance
safeWeight = math.max(newWeight, 0.01)
safeATR = newATR > 0 ? newATR : atr
hasSwingEvidence = not na(newStructuralSwingProminence)
hasVolumeEvidence = not na(newStructuralVolumeScore)
structuralSwingProminence =
hasSwingEvidence ?
clamp01(newStructuralSwingProminence) :
0.0
structuralVolumeScore =
hasVolumeEvidence ?
clamp01(newStructuralVolumeScore) :
0.0
if nearestIndex == -1
initialMemberMin = newPrice
initialMemberMax = newPrice
initialMemberCount = 1
initialTouchBar = countsAsTouch ? touchBar : newBar
[initialZoneLow, initialZoneHigh] = getPoolZoneBounds(
initialMemberMin,
initialMemberMax,
initialMemberCount,
newPrice,
safeATR
)
array.push(prices, newPrice)
array.push(touches, 1)
array.push(bars, newBar)
array.push(activeBars, newActiveBar)
array.push(strengths, newStrength)
array.push(volumes, newVolume)
array.push(sources, newSource)
array.push(zoneLows, initialZoneLow)
array.push(zoneHighs, initialZoneHigh)
array.push(memberMins, initialMemberMin)
array.push(memberMaxs, initialMemberMax)
array.push(memberCounts, initialMemberCount)
array.push(totalWeights, safeWeight)
array.push(lastTouchBars, initialTouchBar)
array.push(poolATRs, safeATR)
array.push(maxSwingProminences, structuralSwingProminence)
array.push(swingEvidenceCounts, hasSwingEvidence ? 1 : 0)
array.push(volumeEvidenceSums, structuralVolumeScore)
array.push(volumeEvidenceCounts, hasVolumeEvidence ? 1 : 0)
array.push(lifecycleStates, LIFECYCLE_FORMING)
array.push(sweepBars, -1)
array.push(beyondCloseCounts, 0)
array.push(resolutionBars, -1)
array.push(lifecycleTouchBars, -1)
else
oldTouches = array.get(touches, nearestIndex)
oldCentroid = array.get(prices, nearestIndex)
oldBar = array.get(bars, nearestIndex)
oldActiveBar = array.get(activeBars, nearestIndex)
oldStrength = array.get(strengths, nearestIndex)
oldVolume = array.get(volumes, nearestIndex)
oldSources = array.get(sources, nearestIndex)
oldMemberMin = array.get(memberMins, nearestIndex)
oldMemberMax = array.get(memberMaxs, nearestIndex)
oldMemberCount = array.get(memberCounts, nearestIndex)
oldTotalWeight = array.get(totalWeights, nearestIndex)
oldLastTouchBar = array.get(lastTouchBars, nearestIndex)
oldPoolATR = array.get(poolATRs, nearestIndex)
oldMaxSwingProminence = array.get(maxSwingProminences, nearestIndex)
oldSwingEvidenceCount = array.get(swingEvidenceCounts, nearestIndex)
oldVolumeEvidenceSum = array.get(volumeEvidenceSums, nearestIndex)
oldVolumeEvidenceCount = array.get(volumeEvidenceCounts, nearestIndex)
combinedWeight = oldTotalWeight + safeWeight
newCentroid = (
oldCentroid * oldTotalWeight +
newPrice * safeWeight
) / combinedWeight
averagedStrength = (
oldStrength * oldTotalWeight +
newStrength * safeWeight
) / combinedWeight
averagedVolume = (
oldVolume * oldTotalWeight +
newVolume * safeWeight
) / combinedWeight
averagedATR = (
oldPoolATR * oldTotalWeight +
safeATR * safeWeight
) / combinedWeight
updatedMemberMin = math.min(oldMemberMin, newPrice)
updatedMemberMax = math.max(oldMemberMax, newPrice)
updatedMemberCount = oldMemberCount + 1
isIndependentTouch =
countsAsTouch and
math.abs(touchBar - oldLastTouchBar) >= independentTouchMinSeparationBars
updatedTouches = oldTouches + (isIndependentTouch ? 1 : 0)
updatedLastTouchBar = isIndependentTouch ? touchBar : oldLastTouchBar
updatedMaxSwingProminence =
hasSwingEvidence ?
math.max(
oldMaxSwingProminence,
structuralSwingProminence
) :
oldMaxSwingProminence
updatedSwingEvidenceCount =
oldSwingEvidenceCount +
(hasSwingEvidence ? 1 : 0)
updatedVolumeEvidenceSum =
oldVolumeEvidenceSum +
(hasVolumeEvidence ? structuralVolumeScore : 0.0)
updatedVolumeEvidenceCount =
oldVolumeEvidenceCount +
(hasVolumeEvidence ? 1 : 0)
[updatedZoneLow, updatedZoneHigh] = getPoolZoneBounds(
updatedMemberMin,
updatedMemberMax,
updatedMemberCount,
newCentroid,
averagedATR
)
array.set(prices, nearestIndex, newCentroid)
array.set(touches, nearestIndex, updatedTouches)
array.set(
bars,
nearestIndex,
math.min(
oldBar,
newBar
)
)
array.set(
activeBars,
nearestIndex,
math.min(
oldActiveBar,
newActiveBar
)
)
array.set(strengths, nearestIndex, averagedStrength)
array.set(volumes, nearestIndex, averagedVolume)
array.set(
sources,
nearestIndex,
appendSource(
oldSources,
newSource
)
)
array.set(zoneLows, nearestIndex, updatedZoneLow)
array.set(zoneHighs, nearestIndex, updatedZoneHigh)
array.set(memberMins, nearestIndex, updatedMemberMin)
array.set(memberMaxs, nearestIndex, updatedMemberMax)
array.set(memberCounts, nearestIndex, updatedMemberCount)
array.set(totalWeights, nearestIndex, combinedWeight)
array.set(lastTouchBars, nearestIndex, updatedLastTouchBar)
array.set(poolATRs, nearestIndex, averagedATR)
array.set(maxSwingProminences, nearestIndex, updatedMaxSwingProminence)
array.set(swingEvidenceCounts, nearestIndex, updatedSwingEvidenceCount)
array.set(volumeEvidenceSums, nearestIndex, updatedVolumeEvidenceSum)
array.set(volumeEvidenceCounts, nearestIndex, updatedVolumeEvidenceCount)
while array.size(prices) > maximumLevels
int oldestIndex = 0
int oldestBar = array.get(activeBars, 0)
if array.size(prices) > 1
for i = 1 to array.size(prices) - 1
candidateBar = array.get(activeBars, i)
if candidateBar < oldestBar
oldestBar := candidateBar
oldestIndex := i
removeLevel(
prices,
touches,
bars,
activeBars,
strengths,
volumes,
sources,
zoneLows,
zoneHighs,
memberMins,
memberMaxs,
memberCounts,
totalWeights,
lastTouchBars,
poolATRs,
maxSwingProminences,
swingEvidenceCounts,
volumeEvidenceSums,
volumeEvidenceCounts,
lifecycleStates,
sweepBars,
beyondCloseCounts,
resolutionBars,
lifecycleTouchBars,
oldestIndex
)
containsSource(
string currentSources,
string sourceName
) =>
wrappedSources =
" + " +
currentSources +
" + "
wrappedSource =
" + " +
sourceName +
" + "
str.contains(
wrappedSources,
wrappedSource
)
getLiquidityDensityScore(int memberCount) =>
memberCount <= 0 ?
0.0 :
clamp01(
0.20 +
0.80 *
(
1.0 -
math.exp(
-0.70 *
float(
math.max(
memberCount - 1,
0
)
)
)
)
)
getTouchQualityScore(int independentTouches) =>
independentTouches <= 0 ?
0.0 :
clamp01(
0.20 +
0.80 *
(
1.0 -
math.exp(
-0.80 *
float(
math.max(
independentTouches - 1,
0
)
)
)
)
)
getTimeframeImportanceScore(string sources) =>
hasPreviousWeek =
containsSource(
sources,
"Previous Week High"
) or
containsSource(
sources,
"Previous Week Low"
)
hasPreviousDay =
containsSource(
sources,
"Previous Day High"
) or
containsSource(
sources,
"Previous Day Low"
)
hasPreviousWeek ?
1.00 :
hasPreviousDay ?
0.70 :
0.35
getLiquidityStrengthComponents(
int memberCount,
int independentTouches,
float maxSwingProminence,
int swingEvidenceCount,
float volumeEvidenceSum,
int volumeEvidenceCount,
string sources
) =>
densityScore =
getLiquidityDensityScore(
memberCount
)
swingProminenceScore =
swingEvidenceCount > 0 ?
clamp01(
maxSwingProminence
) :
0.0
touchQualityScore =
getTouchQualityScore(
independentTouches
)
timeframeImportanceScore =
getTimeframeImportanceScore(
sources
)
formationVolumeScore =
volumeEvidenceCount > 0 ?
clamp01(
volumeEvidenceSum /
float(volumeEvidenceCount)
) :
0.0
liquidityStrength =
100.0 *
(
densityScore * 0.30 +
swingProminenceScore * 0.25 +
touchQualityScore * 0.20 +
timeframeImportanceScore * 0.15 +
formationVolumeScore * 0.10
)
[densityScore, swingProminenceScore, touchQualityScore, timeframeImportanceScore, formationVolumeScore, liquidityStrength]
getPoolLiquidityStrengthAtIndex(
array<int> touches,
array<string> sources,
array<int> memberCounts,
array<float> maxSwingProminences,
array<int> swingEvidenceCounts,
array<float> volumeEvidenceSums,
array<int> volumeEvidenceCounts,
int index
) =>
float result = na
if index >= 0 and index < array.size(memberCounts)
[densityScore, prominenceScore, touchScore, timeframeScore, volumeScore, liquidityStrength] = getLiquidityStrengthComponents(
array.get(memberCounts, index),
array.get(touches, index),
array.get(maxSwingProminences, index),
array.get(swingEvidenceCounts, index),
array.get(volumeEvidenceSums, index),
array.get(volumeEvidenceCounts, index),
array.get(sources, index)
)
result := liquidityStrength
result
getPoolLiquidityComponentsAtIndex(
array<int> touches,
array<string> sources,
array<int> memberCounts,
array<float> maxSwingProminences,
array<int> swingEvidenceCounts,
array<float> volumeEvidenceSums,
array<int> volumeEvidenceCounts,
int index
) =>
float densityScore = na
float prominenceScore = na
float touchScore = na
float timeframeScore = na
float volumeScore = na
float liquidityStrength = na
if index >= 0 and index < array.size(memberCounts)
[componentDensity, componentProminence, componentTouch, componentTimeframe, componentVolume, componentStrength] = getLiquidityStrengthComponents(
array.get(memberCounts, index),
array.get(touches, index),
array.get(maxSwingProminences, index),
array.get(swingEvidenceCounts, index),
array.get(volumeEvidenceSums, index),
array.get(volumeEvidenceCounts, index),
array.get(sources, index)
)
densityScore := componentDensity
prominenceScore := componentProminence
touchScore := componentTouch
timeframeScore := componentTimeframe
volumeScore := componentVolume
liquidityStrength := componentStrength
[densityScore, prominenceScore, touchScore, timeframeScore, volumeScore, liquidityStrength]
getDistanceToZoneATR(
float zoneLow,
float zoneHigh,
float currentATR
) =>
float distanceATR = 0.0
if currentATR > 0
distanceATR :=
close < zoneLow ?
(zoneLow - close) / currentATR :
close > zoneHigh ?
(close - zoneHigh) / currentATR :
0.0
distanceATR
getBaseFreshnessHalfLife(
string sources,
int memberCount,
int independentTouches
) =>
hasPreviousWeek =
containsSource(sources, "Previous Week High") or
containsSource(sources, "Previous Week Low")
hasPreviousDay =
containsSource(sources, "Previous Day High") or
containsSource(sources, "Previous Day Low")
hasClusterEvidence =
memberCount >= 2 or
independentTouches >= 2 or
containsSource(sources, "Equal High") or
containsSource(sources, "Equal Low") or
containsSource(sources, "Repeated Rejection")
hasPreviousWeek ?
float(previousWeekFreshnessHalfLifeBars) :
hasPreviousDay ?
float(previousDayFreshnessHalfLifeBars) :
hasClusterEvidence ?
float(clusterFreshnessHalfLifeBars) :
float(swingFreshnessHalfLifeBars)
getFreshnessComponents(
int activeBar,
float liquidityStrength,
string sources,
int memberCount,
int independentTouches
) =>
age = math.max(bar_index - activeBar, 0)
baseHalfLife = getBaseFreshnessHalfLife(
sources,
memberCount,
independentTouches
)
strengthNormalized = clamp01(
liquidityStrength / 100.0
)
strengthHalfLifeMultiplier =
0.75 +
0.75 *
strengthNormalized
effectiveHalfLife = math.max(
1.0,
baseHalfLife * strengthHalfLifeMultiplier
)
freshness = math.exp(
-0.6931471805599453 *
float(age) /
effectiveHalfLife
)
[age, baseHalfLife, effectiveHalfLife, clamp01(freshness)]
var table wm = table.new(position.bottom_right, 1, 1)
if barstate.isfirst
table.cell(wm, 0, 0, "Telegram @SimpleForexTools", text_color = color.new(color.gray, 70), text_size = size.large)
getAdaptiveTrendInfluenceScale(float timeframeSeconds) =>
float adaptiveScale = 1.00
if not na(timeframeSeconds) and timeframeSeconds > 0.0
fiveMinutes = 5.0 * 60.0
fifteenMinutes = 15.0 * 60.0
fourHours = 4.0 * 60.0 * 60.0
oneDay = 24.0 * 60.0 * 60.0
if timeframeSeconds <= fiveMinutes
adaptiveScale := 2.00
else if timeframeSeconds < fifteenMinutes
progress = math.log(timeframeSeconds / fiveMinutes) / math.log(fifteenMinutes / fiveMinutes)
adaptiveScale := 2.00 + 0.50 * progress
else if timeframeSeconds <= fourHours
adaptiveScale := 2.50
else if timeframeSeconds < oneDay
progress = math.log(timeframeSeconds / fourHours) / math.log(oneDay / fourHours)
adaptiveScale := 2.50 - 1.50 * progress
else
adaptiveScale := 1.00
math.max(1.00, math.min(2.50, adaptiveScale))
getTrendModifier(
float trendBias,
int direction
) =>
directionalTrend = clampSigned(
float(direction) * trendBias
)
adaptiveTrendScale = getAdaptiveTrendInfluenceScale(
chartTimeframeSeconds
)
math.max(
0.75,
math.min(
1.25,
1.0 + 0.10 * adaptiveTrendScale * directionalTrend
)
)
getRegimeModifier(
string currentRegime,
float trendBias,
int direction,
string sources
) =>
directionalTrend = clampSigned(
float(direction) * trendBias
)
isRangeStructure =
containsSource(sources, "Equal High") or
containsSource(sources, "Equal Low") or
containsSource(sources, "Repeated Rejection")
rawModifier =
currentRegime == "Trend Expansion" ?
1.0 + 0.10 * directionalTrend :
currentRegime == "Trend" ?
1.0 + 0.05 * directionalTrend :
currentRegime == "Range" ?
(isRangeStructure ? 1.05 : 1.00) :
currentRegime == "Compression" ?
0.92 :
1.00
boundedModifier = math.max(
0.90,
math.min(
1.10,
rawModifier
)
)
scaledModifier = 1.0 + (boundedModifier - 1.0) * 2.00
math.max(
0.80,
math.min(
1.20,
scaledModifier
)
)
getGravityComponents(
float zoneLow,
float zoneHigh,
float liquidityStrength,
int activeBar,
int memberCount,
int independentTouches,
string sources,
float currentATR,
float trendBias,
int direction,
string currentRegime
) =>
[age, baseHalfLife, effectiveHalfLife, freshness] = getFreshnessComponents(
activeBar,
liquidityStrength,
sources,
memberCount,
independentTouches
)
distanceATR = getDistanceToZoneATR(
zoneLow,
zoneHigh,
currentATR
)
normalizedDistance =
accessibilityKATR > 0 ?
distanceATR / accessibilityKATR :
distanceATR
accessibility = 1.0 / (
1.0 +
normalizedDistance * normalizedDistance
)
distanceMultiplier =
0.55 +
0.45 *
accessibility
freshnessMultiplier =
0.70 +
0.30 *
freshness
trendModifier = getTrendModifier(
trendBias,
direction
)
regimeModifier = getRegimeModifier(
currentRegime,
trendBias,
direction,
sources
)
gravity = math.max(
0.0,
math.min(
100.0,
liquidityStrength *
distanceMultiplier *
freshnessMultiplier *
trendModifier *
regimeModifier
)
)
[age, baseHalfLife, effectiveHalfLife, freshness, distanceATR, accessibility, distanceMultiplier, freshnessMultiplier, trendModifier, regimeModifier, gravity]
getGravityScore(
float zoneLow,
float zoneHigh,
float liquidityStrength,
int activeBar,
int memberCount,
int independentTouches,
string sources,
float currentATR,
float trendBias,
int direction,
string currentRegime
) =>
[age, baseHalfLife, effectiveHalfLife, freshness, distanceATR, accessibility, distanceMultiplier, freshnessMultiplier, trendModifier, regimeModifier, gravity] = getGravityComponents(
zoneLow,
zoneHigh,
liquidityStrength,
activeBar,
memberCount,
independentTouches,
sources,
currentATR,
trendBias,
direction,
currentRegime
)
gravity
getBestIndex(
array<float> prices,
array<int> touches,
array<int> activeBars,
array<string> sources,
array<float> zoneLows,
array<float> zoneHighs,
array<int> memberCounts,
array<float> maxSwingProminences,
array<int> swingEvidenceCounts,
array<float> volumeEvidenceSums,
array<int> volumeEvidenceCounts,
array<int> lifecycleStates,
int exclude1,
int exclude2,
float currentATR,
float trendBias,
int direction,
string currentRegime
) =>
int bestIndex = -1
float bestScore = na
if array.size(prices) > 0
for i = 0 to array.size(prices) - 1
if i != exclude1 and i != exclude2
levelPrice = array.get(prices, i)
zoneLow = array.get(zoneLows, i)
zoneHigh = array.get(zoneHighs, i)
poolSources = array.get(sources, i)
memberCount = array.get(memberCounts, i)
independentTouches = array.get(touches, i)
lifecycleState = array.get(lifecycleStates, i)
rankEligible =
lifecycleState == LIFECYCLE_ACTIVE or
lifecycleState == LIFECYCLE_APPROACHING
correctSide =
direction == 1 ?
zoneHigh >= close :
zoneLow <= close
distanceATR = getDistanceToZoneATR(
zoneLow,
zoneHigh,
currentATR
)
if rankEligible and correctSide and distanceATR <= maxTargetDistanceATR
[densityScore, swingProminenceScore, touchQualityScore, timeframeImportanceScore, formationVolumeScore, liquidityStrength] = getLiquidityStrengthComponents(
memberCount,
independentTouches,
array.get(maxSwingProminences, i),
array.get(swingEvidenceCounts, i),
array.get(volumeEvidenceSums, i),
array.get(volumeEvidenceCounts, i),
poolSources
)
candidateScore = getGravityScore(
zoneLow,
zoneHigh,
liquidityStrength,
array.get(activeBars, i),
memberCount,
independentTouches,
poolSources,
currentATR,
trendBias,
direction,
currentRegime
)
if na(bestScore) or candidateScore > bestScore
bestScore := candidateScore
bestIndex := i
[bestIndex, bestScore]
getRankContribution(
float score,
float rankWeight
) =>
na(score) ?
0.0 :
score * rankWeight
getRankWeightedPull(
float primaryScore,
float secondaryScore,
float tertiaryScore
) =>
getRankContribution(
primaryScore,
primaryRankWeight
) +
getRankContribution(
secondaryScore,
secondaryRankWeight
) +
getRankContribution(
tertiaryScore,
tertiaryRankWeight
)
getLifecycleStateName(int state) =>
string stateName = "Forming"
if state == LIFECYCLE_ACTIVE
stateName := "Active"
else if state == LIFECYCLE_APPROACHING
stateName := "Approaching"
else if state == LIFECYCLE_SWEEPING
stateName := "Sweeping"
else if state == LIFECYCLE_REJECTED
stateName := "Rejected"
else if state == LIFECYCLE_ACCEPTED
stateName := "Accepted"
else if state == LIFECYCLE_RETIRED
stateName := "Retired"
stateName
getLatestLifecycleEventIndex(
array<int> lifecycleStates,
array<int> sweepBars,
array<int> resolutionBars
) =>
int bestIndex = -1
int bestEventBar = -1
if array.size(lifecycleStates) > 0
for i = 0 to array.size(lifecycleStates) - 1
state = array.get(lifecycleStates, i)
eventBar =
state == LIFECYCLE_SWEEPING ? array.get(sweepBars, i) :
(state == LIFECYCLE_REJECTED or state == LIFECYCLE_ACCEPTED) ? array.get(resolutionBars, i) :
-1
if eventBar > bestEventBar
bestEventBar := eventBar
bestIndex := i
[bestIndex, bestEventBar]
getLifecycleEventColor(
int direction,
int state
) =>
color result = #F5B942
if state == LIFECYCLE_ACCEPTED
result := direction == 1 ? bullColor : bearColor
else if state == LIFECYCLE_REJECTED
result := direction == 1 ? bearColor : bullColor
result
getLifecycleEventText(
int direction,
int state
) =>
string result = "SWEEPING"
if state == LIFECYCLE_ACCEPTED
result := direction == 1 ? "✓ ACCEPTED ↑" : "✓ ACCEPTED ↓"
else if state == LIFECYCLE_REJECTED
result := direction == 1 ? "↩ REJECTED ↓" : "↩ REJECTED ↑"
else if state == LIFECYCLE_SWEEPING
result := direction == 1 ? "SWEEPING ↑" : "SWEEPING ↓"
result
pricesVisuallyCollide(
float priceA,
float priceB,
float currentATR
) =>
(
labelCollisionMode == "Auto" and
not na(priceA) and
not na(priceB) and
currentATR > 0 and
math.abs(priceA - priceB) / currentATR <= labelCollisionThresholdATR
)
reserveTargetLabelLane(
float targetPrice,
array<float> assignedPrices,
array<int> assignedLanes,
float currentATR
) =>
int lane = 0
if labelCollisionMode == "Auto" and
not na(targetPrice) and
currentATR > 0
bool lane0Used = false
bool lane1Used = false
bool lane2Used = false
bool lane3Used = false
bool lane4Used = false
bool lane5Used = false
if array.size(assignedPrices) > 0
for i = 0 to array.size(assignedPrices) - 1
otherPrice = array.get(assignedPrices, i)
otherLane = array.get(assignedLanes, i)
if pricesVisuallyCollide(
targetPrice,
otherPrice,
currentATR
)
if otherLane == 0
lane0Used := true
else if otherLane == 1
lane1Used := true
else if otherLane == 2
lane2Used := true
else if otherLane == 3
lane3Used := true
else if otherLane == 4
lane4Used := true
else if otherLane == 5
lane5Used := true
lane :=
not lane0Used ? 0 :
not lane1Used ? 1 :
not lane2Used ? 2 :
not lane3Used ? 3 :
not lane4Used ? 4 :
not lane5Used ? 5 :
5
if not na(targetPrice)
array.push(assignedPrices, targetPrice)
array.push(assignedLanes, lane)
lane
getTargetLaneOffsetBars(
int lane
) =>
safeLane = math.max(lane, 0)
safeLane * rankMarkerSpacingBars +
2 * safeLane * (safeLane - 1)
drawLifecycleEvent(
float levelPrice,
float zoneLow,
float zoneHigh,
int state,
int eventBar,
int direction,
int markerX,
color eventColor
) =>
if showLifecycleEvents and
not na(levelPrice) and
eventBar >= 0 and
(
state == LIFECYCLE_SWEEPING or
state == LIFECYCLE_REJECTED or
state == LIFECYCLE_ACCEPTED
)
eventPrice =
direction == 1 ? zoneHigh : zoneLow
eventStartX = math.max(
eventBar,
bar_index - 12
)
eventEndX = markerX
if showGlow
eventGlow = line.new(
eventStartX,
eventPrice,
eventEndX,
eventPrice,
xloc = xloc.bar_index,
extend = extend.none,
color = color.new(eventColor, 88),
style = line.style_solid,
width = 7
)
array.push(displayLines, eventGlow)
eventLine = line.new(
eventStartX,
eventPrice,
eventEndX,
eventPrice,
xloc = xloc.bar_index,
extend = extend.none,
color = color.new(eventColor, 5),
style = state == LIFECYCLE_SWEEPING ? line.style_dashed : line.style_solid,
width = 2
)
eventLabel = label.new(
eventEndX + 1,
eventPrice,
getLifecycleEventText(direction, state) +
"\n" +
formatPrice(levelPrice),
xloc = xloc.bar_index,
style = label.style_label_left,
color = color.new(eventColor, 72),
textcolor = eventColor,
size = size.tiny,
textalign = text.align_left
)
array.push(displayLines, eventLine)
array.push(displayLabels, eventLabel)
updateLifecycle(
array<float> prices,
array<int> touches,
array<int> activeBars,
array<string> sources,
array<float> zoneLows,
array<float> zoneHighs,
array<int> memberCounts,
array<float> maxSwingProminences,
array<int> swingEvidenceCounts,
array<float> volumeEvidenceSums,
array<int> volumeEvidenceCounts,
array<int> lifecycleStates,
array<int> sweepBars,
array<int> beyondCloseCounts,
array<int> resolutionBars,
array<int> lifecycleTouchBars,
float currentATR,
int direction,
bool commitTransitions
) =>
bool touchedEvent = false
bool sweepEvent = false
bool rejectedEvent = false
bool acceptedEvent = false
if commitTransitions and array.size(prices) > 0 and currentATR > 0
for i = 0 to array.size(prices) - 1
currentState = array.get(lifecycleStates, i)
zoneLow = array.get(zoneLows, i)
zoneHigh = array.get(zoneHighs, i)
activeBar = array.get(activeBars, i)
poolSources = array.get(sources, i)
memberCount = array.get(memberCounts, i)
independentTouches = array.get(touches, i)
[densityScore, swingProminenceScore, touchQualityScore, timeframeImportanceScore, formationVolumeScore, liquidityStrength] = getLiquidityStrengthComponents(
memberCount,
independentTouches,
array.get(maxSwingProminences, i),
array.get(swingEvidenceCounts, i),
array.get(volumeEvidenceSums, i),
array.get(volumeEvidenceCounts, i),
poolSources
)
distanceATR = getDistanceToZoneATR(
zoneLow,
zoneHigh,
currentATR
)
zoneTouched =
direction == 1 ?
high >= zoneLow :
low <= zoneHigh
sweepBoundary =
direction == 1 ?
zoneHigh + currentATR * sweepBufferATR :
zoneLow - currentATR * sweepBufferATR
swept =
direction == 1 ?
high >= sweepBoundary :
low <= sweepBoundary
closeBeyond =
direction == 1 ?
close > zoneHigh :
close < zoneLow
closeOriginalSide =
direction == 1 ?
close < zoneLow :
close > zoneHigh
if currentState == LIFECYCLE_FORMING or
currentState == LIFECYCLE_ACTIVE or
currentState == LIFECYCLE_APPROACHING
if swept and bar_index >= activeBar
initialBeyondCount = closeBeyond ? 1 : 0
array.set(sweepBars, i, bar_index)
array.set(beyondCloseCounts, i, initialBeyondCount)
sweepEvent := true
if initialBeyondCount >= acceptanceCloseCount
array.set(lifecycleStates, i, LIFECYCLE_ACCEPTED)
array.set(resolutionBars, i, bar_index)
acceptedEvent := true
else
array.set(lifecycleStates, i, LIFECYCLE_SWEEPING)
else
nextState =
liquidityStrength < activeLiquidityStrengthThreshold ?
LIFECYCLE_FORMING :
distanceATR <= approachingATR ?
LIFECYCLE_APPROACHING :
LIFECYCLE_ACTIVE
array.set(lifecycleStates, i, nextState)
lastLifecycleTouchBar = array.get(lifecycleTouchBars, i)
newTouchEvent =
zoneTouched and
not swept and
bar_index > activeBar and
(
lastLifecycleTouchBar < 0 or
bar_index - lastLifecycleTouchBar >= independentTouchMinSeparationBars
)
if newTouchEvent
array.set(lifecycleTouchBars, i, bar_index)
touchedEvent := true
else if currentState == LIFECYCLE_SWEEPING
sweepBar = array.get(sweepBars, i)
barsSinceSweep = bar_index - sweepBar
previousBeyondCount = array.get(beyondCloseCounts, i)
updatedBeyondCount = closeBeyond ? previousBeyondCount + 1 : 0
array.set(beyondCloseCounts, i, updatedBeyondCount)
if updatedBeyondCount >= acceptanceCloseCount
array.set(lifecycleStates, i, LIFECYCLE_ACCEPTED)
array.set(resolutionBars, i, bar_index)
acceptedEvent := true
else if barsSinceSweep <= rejectionWindowBars and closeOriginalSide
array.set(lifecycleStates, i, LIFECYCLE_REJECTED)
array.set(resolutionBars, i, bar_index)
rejectedEvent := true
else if barsSinceSweep > rejectionWindowBars
array.set(lifecycleStates, i, LIFECYCLE_RETIRED)
array.set(resolutionBars, i, bar_index)
else if currentState == LIFECYCLE_REJECTED or
currentState == LIFECYCLE_ACCEPTED
resolutionBar = array.get(resolutionBars, i)
if resolutionBar >= 0 and
bar_index - resolutionBar >= lifecycleEventDisplayBars
array.set(lifecycleStates, i, LIFECYCLE_RETIRED)
array.set(resolutionBars, i, bar_index)
[touchedEvent, sweepEvent, rejectedEvent, acceptedEvent]
drawLevel(
float levelPrice,
float score,
string levelName,
string state,
int levelBar,
color levelColor,
int markerX
) =>
if not na(levelPrice) and
not na(score)
startX = math.max(
levelBar,
bar_index -
lineHistoryBars
)
isPrimary = levelName == "Primary Magnet"
isApproaching = state == "Approaching"
lineWidth = isPrimary ? 2 : 1
glowWidth = isPrimary ? 7 : 5
circleTransparency = isApproaching ? 0 : 10
labelTransparency = isApproaching ? 66 : 78
if showGlow
glowLine = line.new(
startX,
levelPrice,
markerX,
levelPrice,
xloc = xloc.bar_index,
extend = extend.none,
color = color.new(
levelColor,
85
),
style = line.style_solid,
width = glowWidth
)
array.push(
displayLines,
Показано первые 50% кода (символов: 74821)
Открыть по тарифу
Купите тариф с пакетом открытий или войдите, если тариф уже есть. Открытый файл останется в вашем кабинете навсегда.
Разовая покупка файла
На этой странице откроется полный исходный код файла вместо ознакомительного фрагмента.
Зачем нужен email? После подтверждения оплаты файл сохранится в кабинете на этот email. Если аккаунта ещё нет, мы создадим его и пришлём ссылку для задания пароля. Если аккаунт уже есть, покупка добавится в него. Укажите адрес, к которому у вас есть доступ.
Чтобы оформить заказ, введите email и перейдите к оплате.