211 lines
8.0 KiB
Plaintext
211 lines
8.0 KiB
Plaintext
|
|
//@version=5
|
||
|
|
indicator("LJH - 테스트 (Table 제거 버전)", overlay=true)
|
||
|
|
|
||
|
|
// === high close, low close ===
|
||
|
|
highClose = ta.highest(high, 12)
|
||
|
|
lowClose = ta.lowest(low, 4)
|
||
|
|
|
||
|
|
// === SMA4 Bands ===
|
||
|
|
sma4 = (close + close[1] + close[2] + close[3]) / 4
|
||
|
|
prevSma4 = (close[4] + close[1] + close[2] + close[3]) / 4
|
||
|
|
prev2Sma4 = (close[4] + close[5] + close[2] + close[3]) / 4
|
||
|
|
prev3Sma4 = (close[4] + close[5] + close[6] + close[3]) / 4
|
||
|
|
|
||
|
|
sma72 = ta.sma(close, 72)
|
||
|
|
sma24 = ta.sma(close, 24)
|
||
|
|
prevSma72 = ta.sma(close, 72)
|
||
|
|
prev2Sma72 = ta.sma(close, 72)
|
||
|
|
prevSma24 = ta.sma(close, 24)
|
||
|
|
prev2Sma24 = ta.sma(close, 24)
|
||
|
|
|
||
|
|
sma4High = ta.highest(sma4, 12)
|
||
|
|
sma4Middle = ta.sma(sma4, 12)
|
||
|
|
sma4Low = ta.lowest(sma4, 12)
|
||
|
|
|
||
|
|
prevSma4High = ta.highest(prevSma4, 12)
|
||
|
|
prevSma4Middle = ta.sma(prevSma4, 12)
|
||
|
|
prevSma4Low = ta.lowest(prevSma4, 12)
|
||
|
|
|
||
|
|
prev2Sma4High = ta.highest(prev2Sma4, 12)
|
||
|
|
prev2Sma4Middle = ta.sma(prev2Sma4, 12)
|
||
|
|
prev2Sma4Low = ta.lowest(prev2Sma4, 12)
|
||
|
|
|
||
|
|
prev3Sma4High = ta.highest(prev3Sma4, 12)
|
||
|
|
prev3Sma4Middle = ta.sma(prev3Sma4, 12)
|
||
|
|
prev3Sma4Low = ta.lowest(prev3Sma4, 12)
|
||
|
|
|
||
|
|
// === 상태 추적 ===
|
||
|
|
var bool inLong = false
|
||
|
|
var bool inShort = false
|
||
|
|
|
||
|
|
// === 새 조건을 위한 상태 변수 ===
|
||
|
|
var int sma4LongTouchBar = na
|
||
|
|
var int sma4ShortTouchBar = na
|
||
|
|
|
||
|
|
// sma4가 sma4Low에 닿은 시점 저장
|
||
|
|
if (sma4 == sma4Low and prevSma4 >= prevSma4Low)
|
||
|
|
sma4LongTouchBar := bar_index
|
||
|
|
|
||
|
|
// sma4가 sma4High에 닿은 시점 저장
|
||
|
|
if (sma4 == sma4High and prevSma4 <= prevSma4High)
|
||
|
|
sma4ShortTouchBar := bar_index
|
||
|
|
|
||
|
|
//전저가 전고가 처리
|
||
|
|
maLength = input.int(4, title="이동평균 기간")
|
||
|
|
ma = ta.sma(close, maLength)
|
||
|
|
|
||
|
|
// 이동평균의 방향 비교
|
||
|
|
maPrev = ma
|
||
|
|
maPrev2 = ma
|
||
|
|
|
||
|
|
// 꼭지점 조건: 상승에서 하락으로 전환 (고점)
|
||
|
|
isTop = maPrev2 < maPrev and maPrev > ma
|
||
|
|
// 꼭지점 조건: 하락에서 상승으로 전환 (저점)
|
||
|
|
isBottom = maPrev2 > maPrev and maPrev < ma
|
||
|
|
|
||
|
|
var float[] topCloses = array.new_float()
|
||
|
|
var float[] bottomCloses = array.new_float()
|
||
|
|
|
||
|
|
if isTop
|
||
|
|
array.push(topCloses, maPrev)
|
||
|
|
if isBottom
|
||
|
|
array.push(bottomCloses, maPrev)
|
||
|
|
|
||
|
|
// === 구름떼 (Ichimoku Cloud) 추가 ===
|
||
|
|
conversionPeriods = 9
|
||
|
|
basePeriods = 26
|
||
|
|
laggingSpan2Periods = 52
|
||
|
|
displacement = 26
|
||
|
|
|
||
|
|
conversionLine = (ta.highest(high, conversionPeriods) + ta.lowest(low, conversionPeriods)) / 2
|
||
|
|
baseLine = (ta.highest(high, basePeriods) + ta.lowest(low, basePeriods)) / 2
|
||
|
|
leadingSpan1 = (conversionLine + baseLine) / 2
|
||
|
|
leadingSpan2 = (ta.highest(high, laggingSpan2Periods) + ta.lowest(low, laggingSpan2Periods)) / 2
|
||
|
|
|
||
|
|
cloudTopNow = math.max(leadingSpan1[displacement], leadingSpan2[displacement])
|
||
|
|
cloudBottomNow = math.min(leadingSpan1[displacement], leadingSpan2[displacement])
|
||
|
|
|
||
|
|
cloudTopAfter = math.max(leadingSpan1, leadingSpan2)
|
||
|
|
cloudBottomAfter = math.min(leadingSpan1, leadingSpan2)
|
||
|
|
|
||
|
|
isSma4BelowCloud = low < cloudBottomNow
|
||
|
|
isSma4AboveCloud = high > cloudTopNow
|
||
|
|
|
||
|
|
var bool hasEnteredShortAfterCloudBreak = false
|
||
|
|
|
||
|
|
// 매 봉마다 상태 업데이트
|
||
|
|
if not isSma4BelowCloud
|
||
|
|
hasEnteredShortAfterCloudBreak := false
|
||
|
|
|
||
|
|
// 상태 변수
|
||
|
|
var bool allowNewShortEntry = true
|
||
|
|
var bool wasBelowCloud = false
|
||
|
|
|
||
|
|
// 구름 위/아래 상태 추적
|
||
|
|
if sma4 < cloudTopNow and not wasBelowCloud
|
||
|
|
allowNewShortEntry := true
|
||
|
|
wasBelowCloud := true
|
||
|
|
|
||
|
|
if not (sma4 < cloudTopNow)
|
||
|
|
allowNewShortEntry := false
|
||
|
|
wasBelowCloud := false
|
||
|
|
|
||
|
|
// === 횡보 구간 감지 ===
|
||
|
|
lengthWindow = 12
|
||
|
|
rangePeriod = 240
|
||
|
|
shortLengthWindow = 4
|
||
|
|
shortRangePeriod = 40
|
||
|
|
|
||
|
|
// Long 기준 비율 계산식
|
||
|
|
longRatio = (high - ta.lowest(low, lengthWindow)) / ta.lowest(low, lengthWindow) * 100
|
||
|
|
// Short 기준 비율 계산식
|
||
|
|
shortRatio = (ta.highest(high, shortLengthWindow) - low) / ta.highest(high, shortLengthWindow) * 100
|
||
|
|
|
||
|
|
// 평균 계산
|
||
|
|
rangeThresholdLongPercent = ta.sma(longRatio, rangePeriod)
|
||
|
|
rangeThresholdShortPercent = ta.sma(shortRatio, shortRangePeriod)
|
||
|
|
|
||
|
|
rangeLookback = 12
|
||
|
|
highestHigh = ta.highest(high, rangeLookback)
|
||
|
|
lowestLow = ta.lowest(low, rangeLookback)
|
||
|
|
|
||
|
|
priceRangeLong = high - lowestLow
|
||
|
|
priceRangeShort = highestHigh - low
|
||
|
|
|
||
|
|
isSidewaysLong = priceRangeLong > (lowestLow * rangeThresholdLongPercent * 1 / 100) and sma4 > cloudTopNow
|
||
|
|
isSidewaysShort = priceRangeShort > (highestHigh * (rangeThresholdShortPercent) / 100) and sma4 < cloudTopNow
|
||
|
|
|
||
|
|
isLongRecentTouch = not na(sma4LongTouchBar) and (bar_index - sma4LongTouchBar <= 10)
|
||
|
|
isShortRecentTouch = not na(sma4ShortTouchBar) and (bar_index - sma4ShortTouchBar <= 5)
|
||
|
|
|
||
|
|
// === 진입/청산 조건 ===
|
||
|
|
rawNewLong = highClose == high and open < close and isLongRecentTouch and isSidewaysLong and prevSma4 <= sma4 and sma4High == sma4 and (prev2Sma4Middle - prevSma4Middle) > (prevSma4Middle - sma4Middle) and sma4Middle > sma72 and sma4 > cloudTopNow
|
||
|
|
rawLongSell = (sma4Middle < sma72 and prevSma4Middle > prevSma72) or (sma4 == sma4Low)
|
||
|
|
|
||
|
|
rawNewShort = (sma72 < cloudTopNow and prevSma4Middle > prevSma24 and sma4Middle > cloudBottomNow and sma4 < sma72 and sma4High > sma72 and sma24 > sma4Middle and sma4 == sma4Low and isSidewaysShort and prevSma4 > sma4 and sma4 < cloudBottomNow) or (sma4 < sma72 and sma4High > sma72 and sma24 < sma4Middle and sma4 == sma4Low and isSidewaysShort and open > close and sma4 < cloudBottomNow and sma4Middle < cloudTopNow)
|
||
|
|
rawShortSell = (sma4High <= sma4 and sma4High < sma72) or sma4 > sma72 or (prevSma4Middle > prevSma4 and sma4Middle < sma4) or (isSidewaysShort and prevSma4Low == prevSma4 and sma4Low < sma4)
|
||
|
|
|
||
|
|
// === 시그널 변수 ===
|
||
|
|
newLongSignal = false
|
||
|
|
longSellSignal = false
|
||
|
|
newShortSignal = false
|
||
|
|
shortSellSignal = false
|
||
|
|
|
||
|
|
// === 롱 진입/청산 로직 ===
|
||
|
|
if not inLong and rawNewLong
|
||
|
|
newLongSignal := true
|
||
|
|
inLong := true
|
||
|
|
|
||
|
|
if inLong and rawLongSell
|
||
|
|
longSellSignal := true
|
||
|
|
inLong := false
|
||
|
|
|
||
|
|
// === 쇼트 진입/청산 로직 ===
|
||
|
|
if not inShort and rawNewShort
|
||
|
|
newShortSignal := true
|
||
|
|
inShort := true
|
||
|
|
|
||
|
|
if inShort and rawShortSell
|
||
|
|
shortSellSignal := true
|
||
|
|
inShort := false
|
||
|
|
|
||
|
|
// === 수익률 계산 및 라벨 표시용 변수 ===
|
||
|
|
var float longEntryPrice = na
|
||
|
|
var float shortEntryPrice = na
|
||
|
|
|
||
|
|
if newLongSignal
|
||
|
|
longEntryPrice := close
|
||
|
|
|
||
|
|
if longSellSignal and not na(longEntryPrice)
|
||
|
|
lastLongProfit = (close - longEntryPrice) / longEntryPrice * 100
|
||
|
|
label.new(bar_index, high, text="롱: " + str.tostring(lastLongProfit, "#.##") + "%", color=color.green, textcolor=color.white, size=size.small)
|
||
|
|
longEntryPrice := na
|
||
|
|
|
||
|
|
if newShortSignal
|
||
|
|
shortEntryPrice := close
|
||
|
|
|
||
|
|
if shortSellSignal and not na(shortEntryPrice)
|
||
|
|
lastShortProfit = (shortEntryPrice - close) / shortEntryPrice * 100
|
||
|
|
label.new(bar_index, low, text="숏: " + str.tostring(lastShortProfit, "#.##") + "%", color=color.red, textcolor=color.white, size=size.small, style=label.style_label_up)
|
||
|
|
shortEntryPrice := na
|
||
|
|
|
||
|
|
// === 시각화 (Plot) ===
|
||
|
|
bgcolor(isSidewaysLong ? color.new(#458558, 85) : na, title="Sideways Long Background")
|
||
|
|
bgcolor(isSidewaysShort ? color.new(#964c4c, 85) : na, title="Sideways Short Background")
|
||
|
|
|
||
|
|
plot(sma4High, color=color.green, linewidth=2, title="SMA(4) High")
|
||
|
|
plot(sma4Low, color=color.red, linewidth=2, title="SMA(4) Low")
|
||
|
|
plot(sma4Middle, color=color.rgb(130, 98, 247), linewidth=2, title="SMA(4) Middle")
|
||
|
|
plot(sma72, color=color.rgb(0, 0, 0), linewidth=2, title="SMA(72)")
|
||
|
|
plot(sma24, color=color.rgb(0, 4, 255), linewidth=2, title="SMA(24)")
|
||
|
|
|
||
|
|
plotshape(newLongSignal, title="Long Entry", location=location.belowbar, color=color.green, style=shape.labelup, text="Long")
|
||
|
|
plotshape(longSellSignal, title="Long Exit", location=location.abovebar, color=color.red, style=shape.labeldown, text="Long Sell")
|
||
|
|
|
||
|
|
plotshape(newShortSignal, title="Short Entry", location=location.abovebar, color=color.rgb(153, 180, 91), style=shape.labeldown, text="Short")
|
||
|
|
plotshape(shortSellSignal, title="Short Exit", location=location.belowbar, color=color.rgb(165, 62, 122), style=shape.labelup, text="Short Sell")
|
||
|
|
|
||
|
|
// 일목균형표 구름대 시각화
|
||
|
|
p1 = plot(leadingSpan1, offset = displacement, title="Leading Span 1", color=color.new(color.green, 30))
|
||
|
|
p2 = plot(leadingSpan2, offset = displacement, title="Leading Span 2", color=color.new(color.red, 30))
|
||
|
|
fill(p1, p2, color = leadingSpan1 > leadingSpan2 ? color.new(color.green, 85) : color.new(color.red, 85), title="Ichimoku Cloud")
|