OPEN-SOURCE SCRIPT

Basee

52
//version=6
indicator("Trade, Travel, Talk AI DATA — Base M5 v1", shorttitle="TTT Base M5", overlay=true, max_boxes_count=300, max_labels_count=50)

// ─────────────────────────────────────────────────────────────────────────────
// Trade, Travel, Talk AI DATA — Base M5
// Detects a compact 1–3 candle base immediately before displacement + BOS.
// Default zone boundaries use the complete wick range (High–Low).
// Designed for XAUUSD M5. Use alerts only after confirming the symbol/session.
// ─────────────────────────────────────────────────────────────────────────────

groupDetect = "1) Base Detection"
enforceM5 = input.bool(true, "ทำงานเฉพาะกราฟ M5", group=groupDetect)
maxBaseBars = input.int(3, "จำนวนแท่ง Base สูงสุด", minval=1, maxval=5, group=groupDetect)
impulseBars = input.int(2, "จำนวนแท่ง Impulse ที่ใช้ยืนยัน", minval=1, maxval=4, group=groupDetect)
atrLength = input.int(14, "ATR Length", minval=2, group=groupDetect)
impulseAtr = input.float(1.20, "แรง Impulse รวมขั้นต่ำ (ATR)", minval=0.20, step=0.05, group=groupDetect)
maxBaseBodyAtr = input.float(0.55, "Body ของ Base สูงสุด (ATR)", minval=0.05, step=0.05, group=groupDetect)
maxBaseRangeAtr = input.float(1.00, "ความกว้าง Base สูงสุด (ATR)", minval=0.10, step=0.05, group=groupDetect)
zoneBoundary = input.string("Wick", "ขอบเขต Base", options=["Wick", "Body"], group=groupDetect)
requireOpposite = input.bool(true, "แท่ง Base ใกล้ Impulse ต้องเป็นสีตรงข้าม", group=groupDetect)
requireSameImpulseColor = input.bool(true, "แท่ง Impulse ต้องไปทิศเดียวกันทั้งหมด", group=groupDetect)
confirmOnClose = input.bool(true, "ยืนยัน Base หลังแท่งปิด (ลด Repaint)", group=groupDetect)

groupStructure = "2) Structure Confirmation"
requireBos = input.bool(true, "ต้องมี BOS", group=groupStructure)
bosLookback = input.int(10, "BOS Lookback", minval=3, maxval=100, group=groupStructure)
fvgBonus = input.bool(true, "เพิ่มคะแนนเมื่อมี FVG", group=groupStructure)

groupLife = "3) Zone Lifecycle"
maxZones = input.int(24, "จำนวนโซนสูงสุด", minval=2, maxval=100, group=groupLife)
rightPaddingBars = input.int(8, "ยื่นกรอบเลยแท่งปัจจุบัน (แท่ง)", minval=1, maxval=100, group=groupLife)
invalidationMode = input.string("Close", "ยกเลิกโซนด้วย", options=["Close", "Wick"], group=groupLife)
invalidationAtrBuffer = input.float(0.05, "Invalidation Buffer (ATR)", minval=0.0, step=0.01, group=groupLife)
deleteInvalid = input.bool(true, "ลบโซนเมื่อ Invalid", group=groupLife)
fadeAfterFirstRetest = input.bool(true, "ลดสีหลัง First Retest", group=groupLife)
showScore = input.bool(true, "แสดงคะแนน Base", group=groupLife)

groupStyle = "4) Style"
buyBorder = input.color(color.rgb(70, 125, 255), "Buy Base Border", group=groupStyle)
sellBorder = input.color(color.rgb(70, 125, 255), "Sell Base Border", group=groupStyle)
buyFill = input.color(color.new(color.rgb(35, 60, 140), 72), "Buy Base Fill", group=groupStyle)
sellFill = input.color(color.new(color.rgb(35, 60, 140), 72), "Sell Base Fill", group=groupStyle)
testedFill = input.color(color.new(color.gray, 88), "Tested Base Fill", group=groupStyle)
showDashboard = input.bool(true, "แสดง Dashboard", group=groupStyle)

bool isM5 = timeframe.isminutes and timeframe.multiplier == 5
bool enabled = not enforceM5 or isM5
float atr = ta.atr(atrLength)

// Zone storage: direction 1 = Buy/Demand, -1 = Sell/Supply.
var box[] zoneBoxes = array.new_box()
var float[] zoneTops = array.new_float()
var float[] zoneBottoms = array.new_float()
var int[] zoneDirections = array.new_int()
var int[] zoneStates = array.new_int() // 0 Fresh, 1 Tested, 2 Invalid (when retained)
var int[] zoneCreatedBars = array.new_int()
var int[] zoneScores = array.new_int()

bool newBuyZone = false
bool newSellZone = false
bool buyRetest = false
bool sellRetest = false
bool buyInvalidated = false
bool sellInvalidated = false

// Confirm a displacement sequence after the base.
float impulseBodySum = 0.0
bool impulseAllBull = true
bool impulseAllBear = true
for j = 0 to impulseBars - 1
impulseBodySum += math.abs(close[j] - open[j])
impulseAllBull := impulseAllBull and close[j] > open[j]
impulseAllBear := impulseAllBear and close[j] < open[j]

bool enoughHistory = bar_index > impulseBars + maxBaseBars + bosLookback + 2
float priorHigh = ta.highest(high[impulseBars + maxBaseBars], bosLookback)
float priorLow = ta.lowest(low[impulseBars + maxBaseBars], bosLookback)
bool strongDisplacement = impulseBodySum >= atr * impulseAtr
bool bullImpulse = strongDisplacement and (not requireSameImpulseColor or impulseAllBull)
bool bearImpulse = strongDisplacement and (not requireSameImpulseColor or impulseAllBear)

// Grow a compact base from the candle nearest the impulse, up to maxBaseBars.
// A lone opposite candle naturally remains a one-candle base; a true compact
// cluster is retained as 2–3 candles instead of being cut down artificially.
int chosenBaseBars = 0
float candidateTop = na
float candidateBottom = na
if enabled and enoughHistory
for n = 1 to maxBaseBars
float tempTop = na
float tempBottom = na
bool bodiesCompact = true
for k = 0 to n - 1
int idx = impulseBars + k
float candleTop = zoneBoundary == "Wick" ? high[idx] : math.max(open[idx], close[idx])
float candleBottom = zoneBoundary == "Wick" ? low[idx] : math.min(open[idx], close[idx])
tempTop := na(tempTop) ? candleTop : math.max(tempTop, candleTop)
tempBottom := na(tempBottom) ? candleBottom : math.min(tempBottom, candleBottom)
bodiesCompact := bodiesCompact and math.abs(close[idx] - open[idx]) <= atr[idx] * maxBaseBodyAtr
bool rangeCompact = tempTop - tempBottom <= atr[impulseBars] * maxBaseRangeAtr
if bodiesCompact and rangeCompact
chosenBaseBars := n
candidateTop := tempTop
candidateBottom := tempBottom

bool bullBaseColor = not requireOpposite or close[impulseBars] <= open[impulseBars]
bool bearBaseColor = not requireOpposite or close[impulseBars] >= open[impulseBars]
bool bullBos = close > priorHigh
bool bearBos = close < priorLow
bool bullFvg = low > high[impulseBars]
bool bearFvg = high < low[impulseBars]

bool confirmationReady = not confirmOnClose or barstate.isconfirmed
bool rawBuySetup = confirmationReady and enabled and enoughHistory and chosenBaseBars > 0 and bullImpulse and bullBaseColor and close > candidateTop and (not requireBos or bullBos)
bool rawSellSetup = confirmationReady and enabled and enoughHistory and chosenBaseBars > 0 and bearImpulse and bearBaseColor and close < candidateBottom and (not requireBos or bearBos)

// Do not recreate an almost identical active zone.
bool duplicateBuy = false
bool duplicateSell = false
if array.size(zoneBoxes) > 0 and chosenBaseBars > 0
for i = 0 to array.size(zoneBoxes) - 1
float oldTop = array.get(zoneTops, i)
float oldBottom = array.get(zoneBottoms, i)
int oldDir = array.get(zoneDirections, i)
float tolerance = atr * 0.10
bool sameArea = math.abs(candidateTop - oldTop) <= tolerance and math.abs(candidateBottom - oldBottom) <= tolerance
duplicateBuy := duplicateBuy or (oldDir == 1 and sameArea)
duplicateSell := duplicateSell or (oldDir == -1 and sameArea)

if rawBuySetup and not duplicateBuy
int score = int(math.min(100.0, 35.0 + math.min(30.0, impulseBodySum / atr * 15.0) + (bullBos ? 20.0 : 0.0) + (fvgBonus and bullFvg ? 10.0 : 0.0) + (chosenBaseBars == 1 ? 5.0 : 0.0)))
string zoneText = "BUY BASE M5 • FRESH" + (showScore ? " • " + str.tostring(score) + "/100" : "")
box newBox = box.new(left=bar_index - (impulseBars + chosenBaseBars - 1), top=candidateTop, right=bar_index + rightPaddingBars, bottom=candidateBottom, xloc=xloc.bar_index, extend=extend.none, border_color=buyBorder, border_width=1, bgcolor=buyFill, text=zoneText, text_color=color.white, text_size=size.tiny, text_halign=text.align_right, text_valign=text.align_center)
array.push(zoneBoxes, newBox)
array.push(zoneTops, candidateTop)
array.push(zoneBottoms, candidateBottom)
array.push(zoneDirections, 1)
array.push(zoneStates, 0)
array.push(zoneCreatedBars, bar_index)
array.push(zoneScores, score)
newBuyZone := true

if rawSellSetup and not duplicateSell
int score = int(math.min(100.0, 35.0 + math.min(30.0, impulseBodySum / atr * 15.0) + (bearBos ? 20.0 : 0.0) + (fvgBonus and bearFvg ? 10.0 : 0.0) + (chosenBaseBars == 1 ? 5.0 : 0.0)))
string zoneText = "SELL BASE M5 • FRESH" + (showScore ? " • " + str.tostring(score) + "/100" : "")
box newBox = box.new(left=bar_index - (impulseBars + chosenBaseBars - 1), top=candidateTop, right=bar_index + rightPaddingBars, bottom=candidateBottom, xloc=xloc.bar_index, extend=extend.none, border_color=sellBorder, border_width=1, bgcolor=sellFill, text=zoneText, text_color=color.white, text_size=size.tiny, text_halign=text.align_right, text_valign=text.align_center)
array.push(zoneBoxes, newBox)
array.push(zoneTops, candidateTop)
array.push(zoneBottoms, candidateBottom)
array.push(zoneDirections, -1)
array.push(zoneStates, 0)
array.push(zoneCreatedBars, bar_index)
array.push(zoneScores, score)
newSellZone := true

// Keep chart objects within the user's selected limit.
while array.size(zoneBoxes) > maxZones
box oldest = array.shift(zoneBoxes)
box.delete(oldest)
array.shift(zoneTops)
array.shift(zoneBottoms)
array.shift(zoneDirections)
array.shift(zoneStates)
array.shift(zoneCreatedBars)
array.shift(zoneScores)

// Update Fresh/Tested/Invalid lifecycle.
if array.size(zoneBoxes) > 0
for i = array.size(zoneBoxes) - 1 to 0
box z = array.get(zoneBoxes, i)
float zTop = array.get(zoneTops, i)
float zBottom = array.get(zoneBottoms, i)
int zDir = array.get(zoneDirections, i)
int zState = array.get(zoneStates, i)
int zCreated = array.get(zoneCreatedBars, i)
int zScore = array.get(zoneScores, i)
float buffer = atr * invalidationAtrBuffer

bool invalidBuy = zState < 2 and zDir == 1 and (invalidationMode == "Close" ? close < zBottom - buffer : low < zBottom - buffer)
bool invalidSell = zState < 2 and zDir == -1 and (invalidationMode == "Close" ? close > zTop + buffer : high > zTop + buffer)
bool invalid = invalidBuy or invalidSell

if invalid
buyInvalidated := buyInvalidated or invalidBuy
sellInvalidated := sellInvalidated or invalidSell
if deleteInvalid
box.delete(z)
array.remove(zoneBoxes, i)
array.remove(zoneTops, i)
array.remove(zoneBottoms, i)
array.remove(zoneDirections, i)
array.remove(zoneStates, i)
array.remove(zoneCreatedBars, i)
array.remove(zoneScores, i)
else
array.set(zoneStates, i, 2)
box.set_extend(z, extend.none)
box.set_right(z, bar_index)
box.set_bgcolor(z, color.new(color.gray, 92))
box.set_border_color(z, color.new(color.gray, 65))
box.set_text(z, (zDir == 1 ? "BUY" : "SELL") + " BASE M5 • INVALID")
else
if zState < 2
box.set_right(z, bar_index + rightPaddingBars)
bool overlapsNow = zState < 2 and high >= zBottom and low <= zTop
bool overlapsPrev = high[1] >= zBottom and low[1] <= zTop
bool firstEntry = bar_index > zCreated and overlapsNow and not overlapsPrev
if zState == 0 and firstEntry
array.set(zoneStates, i, 1)
buyRetest := buyRetest or zDir == 1
sellRetest := sellRetest or zDir == -1
if fadeAfterFirstRetest
box.set_bgcolor(z, testedFill)
box.set_text(z, (zDir == 1 ? "BUY" : "SELL") + " BASE M5 • TESTED" + (showScore ? " • " + str.tostring(zScore) + "/100" : ""))

// Compact dashboard.
var table dashboard = table.new(position.top_right, 2, 4, border_width=1, border_color=color.new(color.aqua, 45))
if barstate.islast and showDashboard
int freshBuy = 0
int freshSell = 0
int tested = 0
if array.size(zoneBoxes) > 0
for i = 0 to array.size(zoneBoxes) - 1
int d = array.get(zoneDirections, i)
int s = array.get(zoneStates, i)
freshBuy += d == 1 and s == 0 ? 1 : 0
freshSell += d == -1 and s == 0 ? 1 : 0
tested += s == 1 ? 1 : 0
table.cell(dashboard, 0, 0, "TTT BASE M5", text_color=color.white, bgcolor=color.rgb(5, 30, 45))
table.cell(dashboard, 1, 0, isM5 ? "ACTIVE" : "USE M5", text_color=isM5 ? color.lime : color.yellow, bgcolor=color.rgb(5, 30, 45))
table.cell(dashboard, 0, 1, "Fresh Buy", text_color=color.white)
table.cell(dashboard, 1, 1, str.tostring(freshBuy), text_color=buyBorder)
table.cell(dashboard, 0, 2, "Fresh Sell", text_color=color.white)
table.cell(dashboard, 1, 2, str.tostring(freshSell), text_color=sellBorder)
table.cell(dashboard, 0, 3, "Tested", text_color=color.white)
table.cell(dashboard, 1, 3, str.tostring(tested), text_color=color.silver)

// Alert events. Create alerts from TradingView's Create Alert dialog.
alertcondition(newBuyZone, "New Buy Base M5", "TTT AI DATA: New BUY Base M5 detected")
alertcondition(newSellZone, "New Sell Base M5", "TTT AI DATA: New SELL Base M5 detected")
alertcondition(buyRetest, "Buy Base First Retest", "TTT AI DATA: Price entered a BUY Base M5 for the first retest")
alertcondition(sellRetest, "Sell Base First Retest", "TTT AI DATA: Price entered a SELL Base M5 for the first retest")
alertcondition(buyInvalidated, "Buy Base Invalidated", "TTT AI DATA: A BUY Base M5 was invalidated")
alertcondition(sellInvalidated, "Sell Base Invalidated", "TTT AI DATA: A SELL Base M5 was invalidated")

คำจำกัดสิทธิ์ความรับผิดชอบ

ข้อมูลและบทความไม่ได้มีวัตถุประสงค์เพื่อก่อให้เกิดกิจกรรมทางการเงิน, การลงทุน, การซื้อขาย, ข้อเสนอแนะ หรือคำแนะนำประเภทอื่น ๆ ที่ให้หรือรับรองโดย TradingView อ่านเพิ่มเติมใน ข้อกำหนดการใช้งาน