← กลับหน้ารายการ

Ultra Reversion DCA Strategy with Manual Leverage - V.1

Strategy ผู้เขียน: TraderCloud_135 Profit Factor: 2.134

ลิงก์ TradingView

เปิดใน TradingView

Equity Chart

Equity chart

เปิดรูปเต็มขนาด

คำอธิบาย

Ultra Reversion DCA Strategy with Manual Leverage - V.1

2025-10-27

รูป Preview

Preview

Pine Script Source

//@version=5
strategy("Ultra Reversion DCA Strategy with Manual Leverage - V.1", shorttitle="Ultra Reversion DCA Strategy - V.1", overlay=true, pyramiding=100)

// --------------------------------------------------------------------------------------------------------------------}
// 포지션 사이징 방식 설정
// --------------------------------------------------------------------------------------------------------------------{
position_sizing_mode = input.string("Martingale", "Position Sizing Mode", options=["Martingale", "Equal Split"], group="Position Sizing")
initial_position_size = input.float(1.0, "Initial Position Size", minval=0.01, step=0.01, group="Position Sizing")

// --------------------------------------------------------------------------------------------------------------------}
// 수동 레버리지 설정
// --------------------------------------------------------------------------------------------------------------------{
manual_leverage = input.float(1.0, "Leverage", minval=1.0, maxval=125.0, step=0.1, group="Leverage Settings")

// 현재 사용 레버리지
current_leverage = manual_leverage

// --------------------------------------------------------------------------------------------------------------------}
// 강제 청산 가격 계산 (자동)
// --------------------------------------------------------------------------------------------------------------------{
var float long_liquidation_price = na
var float short_liquidation_price = na

// 강제 청산 가격 계산 함수 (레버리지에 따른 자동 계산)
// 일반적으로 선물거래소에서는 레버리지 대비 약 95-98% 지점에서 강제청산
calc_liquidation_price(entry_price, leverage, is_long) =>
    liquidation_threshold = 0.95 // 95% 지점에서 강제청산 (5% 마진)
    if is_long
        entry_price * (1 - liquidation_threshold / leverage)
    else
        entry_price * (1 + liquidation_threshold / leverage)

// --------------------------------------------------------------------------------------------------------------------}
// Donchian Channels 설정
// --------------------------------------------------------------------------------------------------------------------{
dc_length = input.int(240, minval=1, title="Donchian Length")
dc_offset = input.int(14, title="Donchian Offset")

// Donchian 채널 계산
dc_lower_raw = ta.lowest(dc_length)
dc_upper_raw = ta.highest(dc_length)

// 오프셋 적용
dc_lower = ta.valuewhen(bar_index, dc_lower_raw, dc_offset)
dc_upper = ta.valuewhen(bar_index, dc_upper_raw, dc_offset)
dc_basis = math.avg(dc_upper, dc_lower)

// Donchian 플롯
plot(dc_basis, "Basis", color=color.rgb(255, 255, 255))
dc_u = plot(dc_upper, "Upper", color=#f1ff29e5)
dc_l = plot(dc_lower, "Lower", color=#f1ff29e5)
fill(dc_u, dc_l, color=#f3f0210d, title="Background")

dc_is_below_lower = open < dc_lower
dc_is_above_upper = open > dc_upper
// 캔들 색상 설정
barcolor(dc_is_below_lower ? color.green : dc_is_above_upper ? color.red : na)

// --------------------------------------------------------------------------------------------------------------------}
// Parabolic RSI 설정
// --------------------------------------------------------------------------------------------------------------------{
rsi_len = input.int(14, "RSI Length", group="RSI")
rsi_upper = input.int(75, "Overbought Threshold", inline="Threshold", group="RSI")
rsi_lower = input.int(25, "Oversold Threshold", inline="Threshold", group="RSI")

display_sar = input.bool(true, "SAR", group="SAR")
sar_start = input.float(0.02, "Start", step=0.01, inline="sar", group="SAR")
sar_inc = input.float(0.022, "Increment", step=0.01, inline="sar", group="SAR")
sar_max = input.float(0.2, "Maximum", step=0.01, inline="sar", group="SAR")
color_up = input.color(#EEA47F, "", inline="c", group="SAR")
color_dn = input.color(#00539C, "", inline="c", group="SAR")

// RSI 계산
rsi = ta.rsi(close, rsi_len)

// Parabolic SAR 함수
pine_sar(src, start, inc, max) =>
    src_high = src + 1
    src_low = src - 1
    var float result = na
    var float maxMin = na
    var float acceleration = na
    var bool isBelow = false
    bool isFirstTrendBar = false
    
    if bar_index <= rsi_len + 2
        if src > src[1]
            isBelow := true
            maxMin := src_high
            result := src_low[1]
        else
            isBelow := false
            maxMin := src_low
            result := src_high[1]
        isFirstTrendBar := true
        acceleration := start
    
    result := nz(result[1], result) + acceleration * (maxMin - nz(result[1], result))
    
    if isBelow
        if result > src_low
            isFirstTrendBar := true
            isBelow := false
            result := math.max(src_high, maxMin)
            maxMin := src_low
            acceleration := start
    else
        if result < src_high
            isFirstTrendBar := true
            isBelow := true
            result := math.min(src_low, maxMin)
            maxMin := src_high
            acceleration := start
    
    if not isFirstTrendBar
        if isBelow
            if src_high > maxMin
                maxMin := src_high
                acceleration := math.min(acceleration + inc, max)
        else
            if src_low < maxMin
                maxMin := src_low
                acceleration := math.min(acceleration + inc, max)
    
    if isBelow
        result := math.min(result, src_low[1])
        if bar_index > 1
            result := math.min(result, src_low[2])
    else
        result := math.max(result, src_high[1])
        if bar_index > 1
            result := math.max(result, src_high[2])
    
    [result, isBelow]

// Parabolic RSI 계산
[sar_rsi, isBelow] = pine_sar(rsi, sar_start, sar_inc, sar_max)

// 신호 계산
sig_up = isBelow != isBelow[1] and isBelow and barstate.isconfirmed
sig_dn = isBelow != isBelow[1] and not isBelow and barstate.isconfirmed
s_sig_up = sig_up and sar_rsi <= rsi_lower
s_sig_dn = sig_dn and sar_rsi >= rsi_upper

// SAR 시각화
var sar = float(na)
if display_sar
    sar := isBelow != isBelow[1] ? na : sar_rsi
sar_col = isBelow ? color_up : color_dn

// RSI 시각화
plotshape(s_sig_up, "Chart Strong RSI Up", shape.diamond, location.belowbar, size=size.small, color=sar_col, force_overlay=true)
plotshape(s_sig_dn, "Chart Strong RSI Down", shape.diamond, location.abovebar, size=size.small, color=sar_col, force_overlay=true)

// --------------------------------------------------------------------------------------------------------------------}
// 양방향 분할 매수/공매도 전략 (레버리지 & 강제청산 적용)
// --------------------------------------------------------------------------------------------------------------------{
// 피라미딩 제한 설정
MAX_PYRAMID = input.int(7, "Max Pyramid Entries", minval=1, maxval=100, title="Pyramiding Limit", group="Strategy Settings")

var float long_total_qty = 0
var float long_total_cost = 0
var float short_total_qty = 0
var float short_total_cost = 0
var int long_entry_count = 0
var int short_entry_count = 0
var string last_long_id = ""
var string last_short_id = ""
var bool long_active = false
var bool short_active = false

// 수익률 추적 변수
var float total_profit_pct = 0
var int total_trades = 0
var float avg_profit_pct = 0

// 청산 버퍼 설정
exit_buffer = input.float(0.1, "Exit Buffer (%)", minval=0.0, step=0.1, title="Take Profit Buffer", group="Strategy Settings")

// 평균 진입 가격 계산
float long_avg_price = long_total_qty > 0 ? long_total_cost / long_total_qty : na
float short_avg_price = short_total_qty > 0 ? short_total_cost / short_total_qty : na

// 강제 청산 가격 업데이트
if long_avg_price and not na(long_avg_price)
    long_liquidation_price := calc_liquidation_price(long_avg_price, current_leverage, true)
if short_avg_price and not na(short_avg_price)
    short_liquidation_price := calc_liquidation_price(short_avg_price, current_leverage, false)

// 포지션 사이즈 계산 함수
calculate_position_size(entry_count, total_qty, is_long) =>
    float qty = 0
    if position_sizing_mode == "Martingale"
        // 마틴게일 방식: 현재 포지션만큼 추가 (1:1:2:4:8:16:32...)
        qty := entry_count == 0 ? initial_position_size : total_qty
    else  // Equal Split
        // 동일 분할 방식: 전체 자금을 피라미딩 횟수로 나누어 진입
        qty := initial_position_size
    qty

// 매수/매도 조건
buy_condition = s_sig_up and dc_is_below_lower
sell_condition = s_sig_dn and dc_is_above_upper

// 청산 조건
close_long_condition = s_sig_dn and close >= (long_avg_price * (100 + exit_buffer) / 100)
close_short_condition = s_sig_up and close <= (short_avg_price * (100 - exit_buffer) / 100)

// 강제 청산 조건
liquidation_long = long_active and close <= long_liquidation_price
liquidation_short = short_active and close >= short_liquidation_price

// 매수 신호 처리
if buy_condition and not short_active and long_entry_count < MAX_PYRAMID
    qty = calculate_position_size(long_entry_count, long_total_qty, true)
    string entry_id = "LongEntry" + str.tostring(bar_index)
    string mode_str = position_sizing_mode == "Martingale" ? "M" : "E"
    strategy.entry(entry_id, strategy.long, qty=qty, comment="Long x" + str.tostring(qty) + " (" + mode_str + ":" + str.tostring(current_leverage) + ")")
    
    long_total_qty := long_total_qty + qty
    long_total_cost := long_total_cost + qty * close
    long_entry_count := long_entry_count + 1
    last_long_id := entry_id
    long_active := true
    
    label.new(bar_index, low, "Long Entry x" + str.tostring(qty) + "\n" + position_sizing_mode + "\nLev:" + str.tostring(current_leverage) + "x (" + str.tostring(long_entry_count) + "/" + str.tostring(MAX_PYRAMID) + ")", 
              color=color.green, style=label.style_label_up, textcolor=color.black, size=size.small)

// 공매도 신호 처리
if sell_condition and not long_active and short_entry_count < MAX_PYRAMID
    qty = calculate_position_size(short_entry_count, short_total_qty, false)
    string entry_id = "ShortEntry" + str.tostring(bar_index)
    string mode_str = position_sizing_mode == "Martingale" ? "M" : "E"
    strategy.entry(entry_id, strategy.short, qty=qty, comment="Short x" + str.tostring(qty) + " (" + mode_str + ":" + str.tostring(current_leverage) + ")")
    
    short_total_qty := short_total_qty + qty
    short_total_cost := short_total_cost + qty * close
    short_entry_count := short_entry_count + 1
    last_short_id := entry_id
    short_active := true
    
    label.new(bar_index, high, "Short Entry x" + str.tostring(qty) + "\n" + position_sizing_mode + "\nLev:" + str.tostring(current_leverage) + "x (" + str.tostring(short_entry_count) + "/" + str.tostring(MAX_PYRAMID) + ")", 
              color=color.red, style=label.style_label_down, textcolor=color.black, size=size.small)

// 강제 청산 처리 (롱)
if liquidation_long and strategy.position_size > 0
    float loss_pct = ((close - long_avg_price) / long_avg_price) * 100
    
    strategy.close_all(comment="LIQUIDATION - Long")
    log.info("LIQUIDATION - Long Position: Loss={0}%", loss_pct)
    
    total_profit_pct := total_profit_pct + loss_pct
    total_trades := total_trades + 1
    avg_profit_pct := total_profit_pct / total_trades
    
    // 강제 청산 알림
    label.new(bar_index, high, "🚨 LIQUIDATION 🚨\nLong Loss: " + str.tostring(loss_pct, "#.##") + "%", 
              color=color.maroon, style=label.style_label_down, textcolor=color.white, size=size.large)
    
    long_total_qty := 0
    long_total_cost := 0
    long_entry_count := 0
    last_long_id := ""
    long_active := false
    long_liquidation_price := na

// 강제 청산 처리 (숏)
if liquidation_short and strategy.position_size < 0
    float loss_pct = ((short_avg_price - close) / short_avg_price) * 100
    
    strategy.close_all(comment="LIQUIDATION - Short")
    log.info("LIQUIDATION - Short Position: Loss={0}%", loss_pct)
    
    total_profit_pct := total_profit_pct + loss_pct
    total_trades := total_trades + 1
    avg_profit_pct := total_profit_pct / total_trades
    
    // 강제 청산 알림
    label.new(bar_index, low, "🚨 LIQUIDATION 🚨\nShort Loss: " + str.tostring(loss_pct, "#.##") + "%", 
              color=color.maroon, style=label.style_label_up, textcolor=color.white, size=size.large)
    
    short_total_qty := 0
    short_total_cost := 0
    short_entry_count := 0
    last_short_id := ""
    short_active := false
    short_liquidation_price := na

// 일반 청산 처리 (롱) - 강제청산이 아닌 경우에만
if close_long_condition and strategy.position_size > 0 and not liquidation_long
    float profit_pct = ((close - long_avg_price) / long_avg_price) * 100
    
    strategy.close_all(comment="Close Long (Profit)")
    log.info("Closing Long Position: Profit={0}%", profit_pct)
    
    total_profit_pct := total_profit_pct + profit_pct
    total_trades := total_trades + 1
    avg_profit_pct := total_profit_pct / total_trades
    
    label.new(bar_index, high, "Close Long\nProfit: " + str.tostring(profit_pct, "#.##") + "%", 
              color=color.orange, style=label.style_label_down, textcolor=color.black)
    
    long_total_qty := 0
    long_total_cost := 0
    long_entry_count := 0
    last_long_id := ""
    long_active := false
    long_liquidation_price := na

// 일반 청산 처리 (숏) - 강제청산이 아닌 경우에만
if close_short_condition and strategy.position_size < 0 and not liquidation_short
    float profit_pct = ((short_avg_price - close) / short_avg_price) * 100
    
    strategy.close_all(comment="Close Short (Profit)")
    log.info("Closing Short Position: Profit={0}%", profit_pct)
    
    total_profit_pct := total_profit_pct + profit_pct
    total_trades := total_trades + 1
    avg_profit_pct := total_profit_pct / total_trades
    
    label.new(bar_index, low, "Close Short\nProfit: " + str.tostring(profit_pct, "#.##") + "%", 
              color=color.orange, style=label.style_label_up, textcolor=color.black)
    
    short_total_qty := 0
    short_total_cost := 0
    short_entry_count := 0
    last_short_id := ""
    short_active := false
    short_liquidation_price := na

// 강제 청산 가격 시각화
plot(long_liquidation_price, "Long Liquidation", color=color.red, style=plot.style_circles, linewidth=2)
plot(short_liquidation_price, "Short Liquidation", color=color.red, style=plot.style_circles, linewidth=2)

// 평단가 시각화
plot(long_total_qty > 0 ? long_avg_price : na, "Long Avg Price", color=color.green, style=plot.style_circles, linewidth=2)
plot(short_total_qty > 0 ? short_avg_price : na, "Short Avg Price", color=color.red, style=plot.style_circles, linewidth=2)

// 메인 정보 테이블 (깔끔한 디자인)
if barstate.islast
    var table main_table = table.new(position.top_left, 2, 4, 
                                   bgcolor=color.new(color.white, 10), 
                                   border_width=1, 
                                   border_color=color.new(color.gray, 30))
    
    // 헤더
    table.cell(main_table, 0, 0, "📊 Strategy Info", 
              text_color=color.white, 
              bgcolor=color.new(color.blue, 0), 
              text_size=size.normal)
    table.cell(main_table, 1, 0, "Values", 
              text_color=color.white, 
              bgcolor=color.new(color.blue, 0), 
              text_size=size.normal)
    
    // 포지션 사이징 모드
    table.cell(main_table, 0, 1, "Sizing Mode", 
              text_color=color.new(color.black, 0), 
              bgcolor=color.new(color.white, 0),
              text_size=size.small)
    table.cell(main_table, 1, 1, position_sizing_mode, 
              text_color=position_sizing_mode == "Martingale" ? color.orange : color.blue,
              bgcolor=color.new(color.white, 0),
              text_size=size.small)
    
    // 레버리지
    table.cell(main_table, 0, 2, "Leverage", 
              text_color=color.new(color.black, 0), 
              bgcolor=color.new(color.white, 0),
              text_size=size.small)
    table.cell(main_table, 1, 2, str.tostring(current_leverage, "#.#") + "x", 
              text_color=current_leverage > 10 ? color.red : current_leverage > 5 ? color.orange : color.green,
              bgcolor=color.new(color.white, 0),
              text_size=size.small)
    
    // 평균 수익률
    table.cell(main_table, 0, 3, "Avg P&L", 
              text_color=color.new(color.black, 0), 
              bgcolor=color.new(color.white, 0),
              text_size=size.small)
    table.cell(main_table, 1, 3, total_trades > 0 ? str.tostring(avg_profit_pct, "#.##") + "%" : "0.00%", 
              text_color=avg_profit_pct > 0 ? color.green : avg_profit_pct < 0 ? color.red : color.gray,
              bgcolor=color.new(color.white, 0),
              text_size=size.small)

// 강제청산 정보 테이블
if barstate.islast and (long_active or short_active)
    var table liq_table = table.new(position.top_center, 2, 3, 
                                   bgcolor=color.new(color.white, 10), 
                                   border_width=1, 
                                   border_color=color.new(color.red, 30))
    
    // 헤더
    table.cell(liq_table, 0, 0, "⚠️ Liquidation (Auto)", 
              text_color=color.white, 
              bgcolor=color.new(color.red, 0), 
              text_size=size.normal)
    table.cell(liq_table, 1, 0, "Prices", 
              text_color=color.white, 
              bgcolor=color.new(color.red, 0), 
              text_size=size.normal)
    
    // 롱 청산가
    table.cell(liq_table, 0, 1, "Long Liq.", 
              text_color=color.new(color.black, 0), 
              bgcolor=color.new(color.white, 0),
              text_size=size.small)
    table.cell(liq_table, 1, 1, long_liquidation_price > 0 ? str.tostring(long_liquidation_price, "#.####") : "N/A", 
              text_color=color.red,
              bgcolor=color.new(color.white, 0),
              text_size=size.small)
    
    // 숏 청산가
    table.cell(liq_table, 0, 2, "Short Liq.", 
              text_color=color.new(color.black, 0), 
              bgcolor=color.new(color.white, 0),
              text_size=size.small)
    table.cell(liq_table, 1, 2, short_liquidation_price > 0 ? str.tostring(short_liquidation_price, "#.####") : "N/A", 
              text_color=color.red,
              bgcolor=color.new(color.white, 0),
              text_size=size.small)

// 포지션 정보 테이블 (컴팩트)
if barstate.islast
    var table pos_table = table.new(position.top_right, 3, 4, 
                                   bgcolor=color.new(color.white, 10), 
                                   border_width=1, 
                                   border_color=color.new(color.gray, 30))
    
    // 헤더
    table.cell(pos_table, 0, 0, "🎯 Position", 
              text_color=color.white, 
              bgcolor=color.new(color.purple, 0), 
              text_size=size.small)
    table.cell(pos_table, 1, 0, "Long", 
              text_color=color.white, 
              bgcolor=color.new(color.green, 0), 
              text_size=size.small)
    table.cell(pos_table, 2, 0, "Short", 
              text_color=color.white, 
              bgcolor=color.new(color.red, 0), 
              text_size=size.small)
    
    // 진입 횟수
    table.cell(pos_table, 0, 1, "Entries", 
              text_color=color.new(color.black, 0), 
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)
    table.cell(pos_table, 1, 1, str.tostring(long_entry_count) + "/" + str.tostring(MAX_PYRAMID), 
              text_color=long_entry_count > 0 ? color.green : color.gray,
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)
    table.cell(pos_table, 2, 1, str.tostring(short_entry_count) + "/" + str.tostring(MAX_PYRAMID), 
              text_color=short_entry_count > 0 ? color.red : color.gray,
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)
    
    // 평균가
    table.cell(pos_table, 0, 2, "Avg Price", 
              text_color=color.new(color.black, 0), 
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)
    table.cell(pos_table, 1, 2, long_avg_price > 0 ? str.tostring(long_avg_price, "#.####") : "N/A", 
              text_color=long_avg_price > 0 ? color.green : color.gray,
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)
    table.cell(pos_table, 2, 2, short_avg_price > 0 ? str.tostring(short_avg_price, "#.####") : "N/A", 
              text_color=short_avg_price > 0 ? color.red : color.gray,
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)
    
    // 다음 진입 물량 예시
    table.cell(pos_table, 0, 3, "Next Size", 
              text_color=color.new(color.black, 0), 
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)
    
    next_long_qty = calculate_position_size(long_entry_count, long_total_qty, true)
    next_short_qty = calculate_position_size(short_entry_count, short_total_qty, false)
    
    table.cell(pos_table, 1, 3, long_entry_count < MAX_PYRAMID ? str.tostring(next_long_qty, "#.##") : "MAX", 
              text_color=long_entry_count < MAX_PYRAMID ? color.green : color.gray,
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)
    table.cell(pos_table, 2, 3, short_entry_count < MAX_PYRAMID ? str.tostring(next_short_qty, "#.##") : "MAX", 
              text_color=short_entry_count < MAX_PYRAMID ? color.red : color.gray,
              bgcolor=color.new(color.white, 0),
              text_size=size.tiny)