//@version=5 strategy("C - LONG - MA driven | HYPE ie500 compound champion", overlay=true, pyramiding=300, initial_capital=500, default_qty_type=strategy.fixed, commission_type=strategy.commission.percent, commission_value=0.05, process_orders_on_close=true, calc_on_order_fills=true, margin_long=100, margin_short=100, max_labels_count=500) // ===================== // Inputs — MDD<50 preset // ===================== // HYPE ie500 grounded compound champion: // t500_b16_s0p25-0p35-0p55_w0p8-1p2-2p2 // equity_start=500, equity_end=2109.57, net=321.91%, // max_mtm_dd=-5.33%, min_trade_mtm=-13.68%. // Initial max target_notional=500; later max notional grows only with earned equity. firstBuyQtyCoin_in = input.float(0.00001, "First Long (coin qty)", minval=0.000001, step=0.000001) minOrderQtyCoin_in = input.float(0.00001, "Min order qty (coin)", minval=0.0, step=0.000001) useEquityPctBase_in = input.bool(true, "Base order as % of sizing equity") baseOrderPctEq_in = input.float(16.0, "Base order % of fixed equity", minval=0.01, step=0.01) equityForSizingUSDT_in = input.float(500.0, "Equity for % sizing (USDT)", minval=0.01, step=0.01) useStrategyEquityForSizing_in = input.bool(true, "Compound: use current strategy equity for sizing") tpPercent_in = input.float(0.52, "TP % (Whole warehouse)", step=0.01) callbackPercent_in = input.float(0.10, "Callback % (Trailing)", step=0.01) marginCallLimit_in = input.int(4, "Margin Call Limit (max longs)", minval=1) linearDropPercent_in = input.float(1.20, "Margin Call Drop % (after lvl 5)", step=0.01) autoMerge_in = input.bool(false, "Auto Merge") subSellTPPercent_in = input.float(0.65, "Sub-sell TP % (last lot)", step=0.01) requireCloseAboveFullTP_in = input.bool(true, "Require close >= Full TP for full close") subSellCloseConfirmMode_in = input.string("breakeven", "Sub-sell close confirmation", options=["off", "breakeven", "subsell_tp"]) requireCloseBelowDcaLevel_in = input.bool(true, "Require close <= Next DCA level") blockDcaOnTpTouch_in = input.bool(false, "Block DCA on Full TP touch even without close confirm") drop1_in = input.float(0.25, "Nonlinear drop lvl2 %", step=0.001) drop2_in = input.float(0.35, "Nonlinear drop lvl3 %", step=0.001) drop3_in = input.float(0.55, "Nonlinear drop lvl4 %", step=0.001) drop4_in = input.float(3.00, "Nonlinear drop lvl5 %", step=0.1) drop5_in = input.float(4.00, "Nonlinear drop lvl6 %", step=0.1) mult2_in = input.float(1.0, "Multiplier lvl2", step=0.001) mult3_in = input.float(1.5, "Multiplier lvl3", step=0.001) mult4_in = input.float(2.75, "Multiplier lvl4", step=0.001) mult5_in = input.float(1.5, "Multiplier lvl5", step=0.1) maxFillsPerBar_in = input.int(2, "Max DCA fills per bar", minval=1, maxval=20) maxSubSellsPerBar_in = input.int(5, "Max SUB-sells per bar", minval=1, maxval=50) useHighLowTouch_in = input.bool(true, "Check trigger touch by Low/High (else Open/Close body)") useLiveSyncStart_in = input.bool(true, "Use live sync start") liveStartTime_in = input.time(0, "Live sync start time (UTC)") maxOrdersPer3Min_in = input.int(6, "Max fills in last 3 min", minval=1, maxval=100) showDebugLabels_in = input.bool(false, "Show orange debug labels") liquidationLinePct_in = input.float(-100.0, "Liquidation line PNL %", step=0.1) useTrendAdaptiveSizing_in = input.bool(false, "Use trend-adaptive long sizing") trendMaTf_in = input.string("D", "Trend MA timeframe") trendMaLen_in = input.int(7, "Trend MA length", minval=1) trendSlopeBars_in = input.int(3, "Trend slope bars", minval=1) trendSlopeLongBoundPct_in = input.float(1.0, "Trend slope long bound %", step=0.1) trendSlopeShortBoundPct_in= input.float(-1.0, "Trend slope short bound %", step=0.1) trendScoreMinPct_in = input.float(45.0, "Trend longness score min %", step=0.1) trendScoreMaxPct_in = input.float(75.0, "Trend longness score max %", step=0.1) minLongInvestPct_in = input.float(0.40, "Min long investment %", step=0.01) maxLongInvestPct_in = input.float(16.0, "Max long investment %", step=0.01) hardBreakevenDeleveragePct_in = input.float(25.0, "Force breakeven deleverage above long exposure %", step=0.1) // MDD guards maxPositionCostPct_in = input.float(100.0, "Max position cost % of sizing equity", minval=0.1, maxval=1000.0, step=0.1) hardMaxTotalDDPct_in = input.float(50.0, "Hard total DD stop %", minval=1.0, maxval=100.0, step=0.1) tvMaxDrawdownStopPct_in = input.float(50.0, "TradingView max drawdown stop %", minval=1.0, maxval=100.0, step=0.1) // TradingView native hard stop strategy.risk.max_drawdown(tvMaxDrawdownStopPct_in, strategy.percent_of_equity) // ===================== // Shared CSV pack // ===================== // Order: // 0 firstBuyQtyCoin // 1 minOrderQtyCoin // 2 useEquityPctBase (0/1) // 3 baseOrderPctEq // 4 equityForSizingUSDT // 5 tpPercent // 6 callbackPercent // 7 marginCallLimit // 8 linearDropPercent // 9 autoMerge (0/1) // 10 subSellTPPercent // 11 requireCloseAboveFullTP (0/1) // 12 subSellCloseConfirmMode (0=off,1=breakeven,2=subsell_tp) // 13 requireCloseBelowDcaLevel (0/1) // 14 blockDcaOnTpTouch (0/1) // 15 drop1 // 16 drop2 // 17 drop3 // 18 drop4 // 19 drop5 // 20 mult2 // 21 mult3 // 22 mult4 // 23 mult5 // 24 maxFillsPerBar // 25 maxSubSellsPerBar // 26 useHighLowTouch (0/1) // 27 useLiveSyncStart (0/1) // 28 liveStartTime (unix ms or 0) // 29 maxOrdersPer3Min useParamPack = input.bool(false, "Use parameter pack CSV") paramPackCsv = input.string("", "Parameter pack CSV") var string[] pack = array.new_string() if barstate.isfirst and useParamPack and str.length(str.replace_all(paramPackCsv, " ", "")) > 0 pack := str.split(paramPackCsv, ",") f_packFloat(_idx, _def) => float v = _def if useParamPack and _idx < array.size(pack) parsed = str.tonumber(str.trim(array.get(pack, _idx))) v := na(parsed) ? _def : parsed v f_packBool(_idx, _def) => f_packFloat(_idx, _def ? 1 : 0) != 0 f_packMode(_idx, _def) => code = int(math.round(f_packFloat(_idx, _def == "off" ? 0 : _def == "breakeven" ? 1 : 2))) code <= 0 ? "off" : code == 1 ? "breakeven" : "subsell_tp" firstBuyQtyCoin = f_packFloat(0, firstBuyQtyCoin_in) minOrderQtyCoin = f_packFloat(1, minOrderQtyCoin_in) useEquityPctBase = f_packBool(2, useEquityPctBase_in) baseOrderPctEq = f_packFloat(3, baseOrderPctEq_in) equityForSizingUSDT_input = f_packFloat(4, equityForSizingUSDT_in) equityForSizingUSDT = useStrategyEquityForSizing_in ? math.max(strategy.equity, 0.0) : equityForSizingUSDT_input tpPercent = f_packFloat(5, tpPercent_in) callbackPercent = f_packFloat(6, callbackPercent_in) marginCallLimit = int(math.round(f_packFloat(7, marginCallLimit_in))) linearDropPercent = f_packFloat(8, linearDropPercent_in) autoMerge = f_packBool(9, autoMerge_in) subSellTPPercent = f_packFloat(10, subSellTPPercent_in) requireCloseAboveFullTP = f_packBool(11, requireCloseAboveFullTP_in) subSellCloseConfirmMode = f_packMode(12, subSellCloseConfirmMode_in) requireCloseBelowDcaLevel = f_packBool(13, requireCloseBelowDcaLevel_in) blockDcaOnTpTouch = f_packBool(14, blockDcaOnTpTouch_in) drop1 = f_packFloat(15, drop1_in) drop2 = f_packFloat(16, drop2_in) drop3 = f_packFloat(17, drop3_in) drop4 = f_packFloat(18, drop4_in) drop5 = f_packFloat(19, drop5_in) mult2 = f_packFloat(20, mult2_in) mult3 = f_packFloat(21, mult3_in) mult4 = f_packFloat(22, mult4_in) mult5 = f_packFloat(23, mult5_in) maxFillsPerBar = int(math.round(f_packFloat(24, maxFillsPerBar_in))) maxSubSellsPerBar = int(math.round(f_packFloat(25, maxSubSellsPerBar_in))) useHighLowTouch = f_packBool(26, useHighLowTouch_in) useLiveSyncStart = f_packBool(27, useLiveSyncStart_in) liveStartTime = int(math.round(f_packFloat(28, liveStartTime_in))) maxOrdersPer3Min = int(math.round(f_packFloat(29, maxOrdersPer3Min_in))) showDebugLabels = showDebugLabels_in liquidationLinePct = liquidationLinePct_in useTrendAdaptiveSizing = useTrendAdaptiveSizing_in trendMaTf = trendMaTf_in trendMaLen = trendMaLen_in trendSlopeBars = trendSlopeBars_in trendSlopeLongBoundPct = trendSlopeLongBoundPct_in trendSlopeShortBoundPct= trendSlopeShortBoundPct_in trendScoreMinPct = trendScoreMinPct_in trendScoreMaxPct = trendScoreMaxPct_in minLongInvestPct = minLongInvestPct_in maxLongInvestPct = maxLongInvestPct_in hardBreakevenDeleveragePct = hardBreakevenDeleveragePct_in maxPositionCostPct = maxPositionCostPct_in hardMaxTotalDDPct = hardMaxTotalDDPct_in // ===================== // Shared parity anchor // ===================== anchorTz = "UTC" anchorTs = timestamp(anchorTz, year, 1, 1, 0, 0) barMs = int(timeframe.in_seconds(timeframe.period) * 1000) barsFromAnchor = int(math.floor((time - anchorTs) / barMs)) afterAnchor = time >= anchorTs allowThisBar = afterAnchor and (barsFromAnchor % 2 == 0) // ===================== // Live sync start // ===================== liveNow = not useLiveSyncStart or time >= liveStartTime livePrev = bar_index > 0 ? (not useLiveSyncStart or time[1] >= liveStartTime) : false liveStartBar = liveNow and not livePrev // ===================== // Order-fill throttle // ===================== barSeconds = timeframe.in_seconds(timeframe.period) barsIn3MinWindow = math.max(1, int(math.ceil(180.0 / barSeconds))) historyBarsLimit = math.max(barsIn3MinWindow - 1, 0) var int ordersThisBar = 0 var int ordersBarIndex = na var int[] recentBarFills = array.new_int() var int recentHistoryFills = 0 var int maxOrdersPerBarBT = 0 var int maxOrdersBarTime = na var int maxOrdersPerWindowBT = 0 var int maxOrdersWindowTime = na var int fullTPWarehouseCloses = 0 var label maxOrdersLabel = na var bool riskStopped = false f_recentOrdersIn3Min() => recentHistoryFills + ordersThisBar if na(ordersBarIndex) ordersBarIndex := bar_index ordersThisBar := 0 else if bar_index != ordersBarIndex if historyBarsLimit > 0 array.push(recentBarFills, ordersThisBar) recentHistoryFills += ordersThisBar if array.size(recentBarFills) > historyBarsLimit removed = array.shift(recentBarFills) recentHistoryFills -= removed else recentHistoryFills := 0 ordersThisBar := 0 ordersBarIndex := bar_index recentOrdersIn3Min = f_recentOrdersIn3Min() if recentOrdersIn3Min > maxOrdersPerWindowBT maxOrdersPerWindowBT := recentOrdersIn3Min maxOrdersWindowTime := time triggerHigh = useHighLowTouch ? high : math.max(open, close) triggerLow = useHighLowTouch ? low : math.min(open, close) f_canPlaceOrder() => liveNow and allowThisBar and not riskStopped and f_recentOrdersIn3Min() < maxOrdersPer3Min // ===================== // Trend adaptive sizing // ===================== trendMa = request.security(syminfo.tickerid, trendMaTf, ta.sma(close, trendMaLen), lookahead=barmerge.lookahead_off) trendMaPrev = trendMa[trendSlopeBars] trendSlopeRawPct = not na(trendMaPrev) and trendMaPrev != 0 ? ((trendMa - trendMaPrev) / trendMaPrev) * 100.0 : 0.0 trendSlopeRange = math.max(math.abs(trendSlopeLongBoundPct - trendSlopeShortBoundPct), 0.000001) trendLongnessPct = math.max(0.0, math.min(100.0, 100.0 * (trendSlopeRawPct - trendSlopeShortBoundPct) / trendSlopeRange)) trendScoreRange = math.max(math.abs(trendScoreMaxPct - trendScoreMinPct), 0.000001) trendSizingFactor = math.max(0.0, math.min(1.0, (trendLongnessPct - trendScoreMinPct) / trendScoreRange)) targetLongInvestPct = useTrendAdaptiveSizing ? (minLongInvestPct + (maxLongInvestPct - minLongInvestPct) * trendSizingFactor) : baseOrderPctEq // ===================== // Helper funcs // ===================== f_getDropForNextLevel(_numBuys) => nb = _numBuys + 1 nb == 2 ? drop1 : nb == 3 ? drop2 : nb == 4 ? drop3 : nb == 5 ? drop4 : nb == 6 ? drop5 : linearDropPercent f_getMultForNextLevel(_numBuys) => nb = _numBuys + 1 nb == 2 ? mult2 : nb == 3 ? mult3 : nb == 4 ? mult4 : nb == 5 ? mult5 : 1.0 f_nextLevel(_lastFillPrice, _numBuys) => d = f_getDropForNextLevel(_numBuys) _lastFillPrice * (1.0 - d / 100.0) f_recalcAvg(_cost, _sizeAbs) => _sizeAbs > 0 ? _cost / _sizeAbs : na f_calcBaseOrderQtyCoin() => sizingPct = useTrendAdaptiveSizing ? targetLongInvestPct : baseOrderPctEq rawQtyCoin = useEquityPctBase ? ((equityForSizingUSDT * sizingPct / 100.0) / close) : firstBuyQtyCoin math.max(rawQtyCoin, minOrderQtyCoin) f_lotTag(_n) => _n == 1 ? "dl1" : _n <= 6 ? "dlN" + str.tostring(_n) : "dl" + str.tostring(_n) f_maxPositionCostUSDT() => equityForSizingUSDT * maxPositionCostPct / 100.0 // ===================== // State // ===================== var float posSizeAbs = 0.0 var float posCostUSDT = 0.0 var float avgPrice = na var int numBuys = 0 var float lastFillPrice = na var float nextLevelPrice = na // LIFO lots var int lotCounter = 0 var string[] lotIds = array.new_string() var float[] lotQty = array.new_float() var float[] lotPrice = array.new_float() var string[] lotTags = array.new_string() var float[] lotUsdt = array.new_float() f_unrealizedPnlUSDT() => float acc = 0.0 int n = array.size(lotQty) if n > 0 for i = 0 to n - 1 acc += array.get(lotQty, i) * (close - array.get(lotPrice, i)) acc f_totalPnlPct(_pnlUsdt) => equityForSizingUSDT > 0 ? (_pnlUsdt / equityForSizingUSDT) * 100.0 : na f_fullSellProfitUSDT(_fillPrice) => float acc = 0.0 int n = array.size(lotQty) if n > 0 for i = 0 to n - 1 acc += array.get(lotQty, i) * (_fillPrice - array.get(lotPrice, i)) acc // Cycle-level counters var int cycleSsCounter = 0 // PNL / liquidation tracking var float realizedPnlUSDT = 0.0 var float minTotalPnlUSDT = 0.0 var float minTotalPnlPct = 0.0 var bool inLiqZone = false var int liqZoneCount = 0 var float liqZoneCurrentMaxTopUp = 0.0 var float liqZoneTotalTopUp = 0.0 var label liqWarnLabel = na // trailing for full TP var bool trailingActive = false var float trailingMax = na var float cycleBaseQtyCoin = na // cycle flags var bool resetCycle = false var bool restartedThisBar = false restartedThisBar := false // ===================== // Bar event aggregation // ===================== var string barEvents = "" var bool barHasAction = false var string barDcaBlock = "" var string barSsBlock = "" if barstate.isnew barEvents := "" barHasAction := false barDcaBlock := "" barSsBlock := "" f_addEvent(_currEvents, _text) => _nextEvents = _currEvents == "" ? _text : _currEvents + "\n" + _text [_nextEvents, true] // ===================== // HARD RESET AT LIVE START // ===================== if liveStartBar posSizeAbs := 0.0 posCostUSDT := 0.0 avgPrice := na numBuys := 0 lastFillPrice := na nextLevelPrice := na trailingActive := false trailingMax := na cycleBaseQtyCoin := na cycleSsCounter := 0 realizedPnlUSDT := 0.0 minTotalPnlUSDT := 0.0 minTotalPnlPct := 0.0 inLiqZone := false liqZoneCount := 0 liqZoneCurrentMaxTopUp := 0.0 liqZoneTotalTopUp := 0.0 riskStopped := false if not na(liqWarnLabel) label.delete(liqWarnLabel) liqWarnLabel := na array.clear(lotIds) array.clear(lotQty) array.clear(lotPrice) array.clear(lotTags) array.clear(lotUsdt) restartedThisBar := false [_barEvents, _barHasAction] = f_addEvent(barEvents, "LIVE SYNC RESET") barEvents := _barEvents barHasAction := _barHasAction // ===================== // RESET + immediate restart // ===================== if liveNow and resetCycle posSizeAbs := 0.0 posCostUSDT := 0.0 avgPrice := na numBuys := 0 lastFillPrice := na nextLevelPrice := na trailingActive := false trailingMax := na cycleBaseQtyCoin := na cycleSsCounter := 0 array.clear(lotIds) array.clear(lotQty) array.clear(lotPrice) array.clear(lotTags) array.clear(lotUsdt) resetCycle := false if f_canPlaceOrder() cycleBaseQtyCoin := f_calcBaseOrderQtyCoin() buyQty = cycleBaseQtyCoin buyUSDT = buyQty * close if buyUSDT <= f_maxPositionCostUSDT() lotCounter += 1 id0 = "L" + str.tostring(lotCounter) tag0 = f_lotTag(1) strategy.entry(id0, strategy.long, qty=buyQty, comment="Restart Long_0") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time qty0 = buyQty fill0 = close array.push(lotIds, id0) array.push(lotQty, qty0) array.push(lotPrice, fill0) array.push(lotTags, tag0) array.push(lotUsdt, buyUSDT) posSizeAbs += qty0 posCostUSDT += buyUSDT avgPrice := fill0 numBuys := 1 lastFillPrice := fill0 nextLevelPrice := f_nextLevel(lastFillPrice, numBuys) info = tag0 + " @" + str.tostring(fill0, "#.####") + " (" + str.tostring(buyQty, "#.######") + "q)" restartedThisBar := true [_barEvents, _barHasAction] = f_addEvent(barEvents, "RESTART " + info) barEvents := _barEvents barHasAction := _barHasAction else [_barEvents, _barHasAction] = f_addEvent(barEvents, "RESTART BLOCKED: cost cap") barEvents := _barEvents barHasAction := _barHasAction // ===================== // Start new cycle if flat // ===================== if liveNow and posSizeAbs == 0 and array.size(lotIds) == 0 and not resetCycle and not restartedThisBar if f_canPlaceOrder() cycleBaseQtyCoin := f_calcBaseOrderQtyCoin() buyQty = cycleBaseQtyCoin buyUSDT = buyQty * close if buyUSDT <= f_maxPositionCostUSDT() lotCounter += 1 id0 = "L" + str.tostring(lotCounter) tag0 = f_lotTag(1) strategy.entry(id0, strategy.long, qty=buyQty, comment="First Long_0") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time qty0 = buyQty fill0 = close array.push(lotIds, id0) array.push(lotQty, qty0) array.push(lotPrice, fill0) array.push(lotTags, tag0) array.push(lotUsdt, buyUSDT) posSizeAbs += qty0 posCostUSDT += buyUSDT avgPrice := fill0 numBuys := 1 lastFillPrice := fill0 nextLevelPrice := f_nextLevel(lastFillPrice, numBuys) info = tag0 + " @" + str.tostring(fill0, "#.####") + " (" + str.tostring(buyQty, "#.######") + "q)" [_barEvents, _barHasAction] = f_addEvent(barEvents, "FIRST " + info) barEvents := _barEvents barHasAction := _barHasAction else [_barEvents, _barHasAction] = f_addEvent(barEvents, "FIRST BLOCKED: cost cap") barEvents := _barEvents barHasAction := _barHasAction // ===================== // TP state // ===================== tpPrice = avgPrice * (1.0 + tpPercent / 100.0) tpTouch = liveNow and not na(avgPrice) and triggerHigh >= tpPrice fullTPCloseOk = not requireCloseAboveFullTP or close >= tpPrice tpCloseConfirmed = tpTouch and fullTPCloseOk tpBlocksDca = blockDcaOnTpTouch ? tpTouch : tpCloseConfirmed // ===================== // FULL TP priority // ===================== if liveNow and tpTouch if callbackPercent > 0 trailingActive := true trailingMax := na(trailingMax) ? triggerHigh : math.max(trailingMax, triggerHigh) trailStop = trailingMax * (1.0 - callbackPercent / 100.0) if tpCloseConfirmed and close <= trailStop and f_canPlaceOrder() strategy.close_all(comment="TP Full (Trailing)") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time realizedPnlUSDT += f_fullSellProfitUSDT(close) fullTPWarehouseCloses += 1 if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time resetCycle := true [_barEvents, _barHasAction] = f_addEvent(barEvents, "FULL CLOSE (trail) @" + str.tostring(close, "#.####")) barEvents := _barEvents barHasAction := _barHasAction else if tpCloseConfirmed and f_canPlaceOrder() strategy.close_all(comment="TP Full") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time realizedPnlUSDT += f_fullSellProfitUSDT(close) fullTPWarehouseCloses += 1 if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time resetCycle := true [_barEvents, _barHasAction] = f_addEvent(barEvents, "FULL CLOSE @" + str.tostring(close, "#.####")) barEvents := _barEvents barHasAction := _barHasAction // ===================== // DCA longs // ===================== if liveNow and not tpBlocksDca and posSizeAbs > 0 and not restartedThisBar canBuyMore = numBuys < marginCallLimit fills = 0 while canBuyMore and fills < maxFillsPerBar and not na(nextLevelPrice) and triggerLow <= nextLevelPrice and (not requireCloseBelowDcaLevel or close <= nextLevelPrice) and f_canPlaceOrder() mult = f_getMultForNextLevel(numBuys) if na(cycleBaseQtyCoin) cycleBaseQtyCoin := f_calcBaseOrderQtyCoin() buyQty = cycleBaseQtyCoin * mult buyUSDT = buyQty * close if posCostUSDT + buyUSDT > f_maxPositionCostUSDT() canBuyMore := false barDcaBlock := barDcaBlock == "" ? "DCA BLOCKED: cost cap" : barDcaBlock + "\nDCA BLOCKED: cost cap" break lotCounter += 1 id = "L" + str.tostring(lotCounter) newLevel = numBuys + 1 tag = f_lotTag(newLevel) triggerLevel = nextLevelPrice strategy.entry(id, strategy.long, qty=buyQty, comment="DCA Long") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time fillP = close qtyB = buyQty array.push(lotIds, id) array.push(lotQty, qtyB) array.push(lotPrice, fillP) array.push(lotTags, tag) array.push(lotUsdt, buyUSDT) posSizeAbs += qtyB posCostUSDT += buyUSDT avgPrice := f_recalcAvg(posCostUSDT, posSizeAbs) numBuys += 1 lastFillPrice := triggerLevel nextLevelPrice := f_nextLevel(lastFillPrice, numBuys) trailingActive := false trailingMax := na dcaLine = tag + " close@" + str.tostring(fillP, "#.####") + " trg@" + str.tostring(triggerLevel, "#.####") + " (" + str.tostring(buyQty, "#.######") + "q)" + " ss-TP≥" + str.tostring(fillP * (1.0 + subSellTPPercent / 100.0), "#.####") barDcaBlock := barDcaBlock == "" ? dcaLine : barDcaBlock + "\n" + dcaLine fills += 1 canBuyMore := numBuys < marginCallLimit if barDcaBlock != "" [_barEvents, _barHasAction] = f_addEvent(barEvents, barDcaBlock) barEvents := _barEvents barHasAction := _barHasAction // ===================== // SUB-SELL // ===================== if liveNow and not tpBlocksDca and posSizeAbs > 0 and numBuys > 5 and not restartedThisBar sold = 0 anySold = false while sold < maxSubSellsPerBar and f_canPlaceOrder() lastIdx = array.size(lotIds) - 1 if lastIdx < 0 break idLast = array.get(lotIds, lastIdx) qtyLast = array.get(lotQty, lastIdx) entryLast = array.get(lotPrice, lastIdx) tagLast = array.get(lotTags, lastIdx) usdtLast = array.get(lotUsdt, lastIdx) currentLongExposurePct = equityForSizingUSDT > 0 ? ((posSizeAbs * close) / equityForSizingUSDT) * 100.0 : 0.0 forceBreakevenDeleverage = currentLongExposurePct > hardBreakevenDeleveragePct lastLotTP = entryLast * (1.0 + subSellTPPercent / 100.0) sellTouchLevel = forceBreakevenDeleverage ? entryLast : lastLotTP subSellTouch = triggerHigh >= sellTouchLevel subSellCloseOk = forceBreakevenDeleverage ? (close >= entryLast) : subSellCloseConfirmMode == "off" ? true : subSellCloseConfirmMode == "breakeven" ? (close >= entryLast) : (close >= lastLotTP) if subSellTouch and subSellCloseOk anySold := true cycleSsCounter += 1 strategy.close(idLast, comment="Sub-sell last lot") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time entryCostUSDT = qtyLast * entryLast profitUSDT = qtyLast * (close - entryLast) posSizeAbs -= qtyLast posCostUSDT -= entryCostUSDT realizedPnlUSDT += profitUSDT if autoMerge posCostUSDT -= profitUSDT avgPrice := f_recalcAvg(posCostUSDT, posSizeAbs) array.pop(lotIds) array.pop(lotQty) array.pop(lotPrice) array.pop(lotTags) array.pop(lotUsdt) numBuys := math.max(numBuys - 1, 0) sold += 1 ssLine = "ss" + str.tostring(cycleSsCounter) + " ← " + tagLast + " @" + str.tostring(entryLast, "#.####") + " (" + str.tostring(qtyLast, "#.######") + "q)" + " TP≥" + str.tostring(sellTouchLevel, "#.####") + " fill @" + str.tostring(close, "#.####") barSsBlock := barSsBlock == "" ? ssLine : barSsBlock + "\n" + ssLine if array.size(lotIds) == 0 or posSizeAbs <= 0 resetCycle := true break else break if barSsBlock != "" [_barEvents, _barHasAction] = f_addEvent(barEvents, barSsBlock) barEvents := _barEvents barHasAction := _barHasAction if anySold and not resetCycle and array.size(lotPrice) > 0 lastFillPrice := array.get(lotPrice, array.size(lotPrice) - 1) nextLevelPrice := f_nextLevel(lastFillPrice, numBuys) // ===================== // PNL / hard DD stop // ===================== unrealizedPnlUSDT = f_unrealizedPnlUSDT() totalPnlUSDT = realizedPnlUSDT + unrealizedPnlUSDT totalPnlPct = f_totalPnlPct(totalPnlUSDT) hardDdHit = liveNow and not riskStopped and not na(totalPnlPct) and totalPnlPct <= -hardMaxTotalDDPct if hardDdHit if posSizeAbs > 0 and array.size(lotIds) > 0 strategy.close_all(comment="HARD DD STOP") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time realizedPnlUSDT += f_fullSellProfitUSDT(close) posSizeAbs := 0.0 posCostUSDT := 0.0 avgPrice := na numBuys := 0 lastFillPrice := na nextLevelPrice := na trailingActive := false trailingMax := na cycleBaseQtyCoin := na resetCycle := false riskStopped := true array.clear(lotIds) array.clear(lotQty) array.clear(lotPrice) array.clear(lotTags) array.clear(lotUsdt) unrealizedPnlUSDT := 0.0 totalPnlUSDT := realizedPnlUSDT totalPnlPct := f_totalPnlPct(totalPnlUSDT) [_barEvents, _barHasAction] = f_addEvent(barEvents, "HARD DD STOP @" + str.tostring(close, "#.####")) barEvents := _barEvents barHasAction := _barHasAction // ===================== // PNL / liquidation state // ===================== thresholdPnlUSDT = equityForSizingUSDT * liquidationLinePct / 100.0 requiredTopUpUSDT = math.max(0.0, thresholdPnlUSDT - totalPnlUSDT) isLiqZone = totalPnlPct < liquidationLinePct if barstate.isconfirmed minTotalPnlUSDT := math.min(minTotalPnlUSDT, totalPnlUSDT) minTotalPnlPct := math.min(minTotalPnlPct, totalPnlPct) if isLiqZone and not inLiqZone liqZoneCount += 1 inLiqZone := true liqZoneCurrentMaxTopUp := requiredTopUpUSDT else if isLiqZone and inLiqZone liqZoneCurrentMaxTopUp := math.max(liqZoneCurrentMaxTopUp, requiredTopUpUSDT) else if not isLiqZone and inLiqZone liqZoneTotalTopUp += liqZoneCurrentMaxTopUp liqZoneCurrentMaxTopUp := 0.0 inLiqZone := false liqTopUpDisplay = liqZoneTotalTopUp + (inLiqZone ? liqZoneCurrentMaxTopUp : 0.0) posNotionalUSDT = posSizeAbs * close // FULL CLOSE label if barstate.isconfirmed and barHasAction and str.contains(barEvents, "FULL CLOSE") label.new( bar_index, close, "★ FULL CLOSE\n" + barEvents, style = label.style_label_down, color = color.yellow, textcolor = color.black, size = size.small) // HARD DD label if barstate.isconfirmed and barHasAction and str.contains(barEvents, "HARD DD STOP") label.new( bar_index, close, "■ HARD DD STOP\n" + barEvents, style = label.style_label_down, color = color.red, textcolor = color.white, size = size.small) // Debug if showDebugLabels and barstate.isconfirmed and ordersThisBar > 1 debugText = "fills/bar=" + str.tostring(ordersThisBar) + "\nrecent3m=" + str.tostring(f_recentOrdersIn3Min()) + "\nhistory3m=" + str.tostring(recentHistoryFills) + "\ntrgH/L=" + str.tostring(triggerHigh, "#.####") + " / " + str.tostring(triggerLow, "#.####") + "\nclose=" + str.tostring(close, "#.####") + "\ntpTouch=" + str.tostring(tpTouch) + "\ntpCloseConfirmed=" + str.tostring(tpCloseConfirmed) + "\ntpBlocksDca=" + str.tostring(tpBlocksDca) + "\nsubMode=" + subSellCloseConfirmMode + "\nposCost=" + str.tostring(posCostUSDT, "#.##") + "\nmaxCost=" + str.tostring(f_maxPositionCostUSDT(), "#.##") + "\nriskStopped=" + str.tostring(riskStopped) label.new( bar_index, high, debugText, style = label.style_label_down, color = color.new(color.orange, 0), textcolor = color.black, size = size.small) // Summary label if barstate.islast if not na(maxOrdersLabel) label.delete(maxOrdersLabel) if not na(liqWarnLabel) label.delete(liqWarnLabel) maxOrdersText = "Avg price: " + str.tostring(avgPrice, "#.####") + "\nPos qty: " + str.tostring(posSizeAbs, "#.######") + "\nPos notional USDT: " + str.tostring(posNotionalUSDT, "#.##") + "\nPos cost USDT: " + str.tostring(posCostUSDT, "#.##") + "\nMax cost cap USDT: " + str.tostring(f_maxPositionCostUSDT(), "#.##") + "\nPNL total USDT: " + str.tostring(totalPnlUSDT, "#.##") + "\nPNL total %: " + str.tostring(totalPnlPct, "#.##") + "\nMax DD total PNL USDT: " + str.tostring(minTotalPnlUSDT, "#.##") + "\nMax DD total PNL %: " + str.tostring(minTotalPnlPct, "#.##") + "\nRisk stopped: " + str.tostring(riskStopped) + "\nMax fills/bar: " + str.tostring(maxOrdersPerBarBT) + "\nMax orders/3m: " + str.tostring(maxOrdersPerWindowBT) + "\nCurrent orders/3m: " + str.tostring(f_recentOrdersIn3Min()) aquaY = high redY = high + ta.atr(14) * 1.5 maxOrdersLabel := label.new( bar_index, aquaY, maxOrdersText, style = label.style_label_left, color = color.new(color.aqua, 0), textcolor = color.black, size = size.small) if liqZoneCount > 0 liqWarnText = "LIQ zones: " + str.tostring(liqZoneCount) + "\nTop-up needed USDT: " + str.tostring(liqTopUpDisplay, "#.##") liqWarnLabel := label.new( bar_index, redY, liqWarnText, style = label.style_label_left, color = color.new(color.red, 0), textcolor = color.white, size = size.small) // ===================== // Plots // ===================== plot(avgPrice, "Avg Price", color=color.new(color.yellow, 0), linewidth=2) plot(tpPrice, "TP Price", color=color.new(color.green, 0), linewidth=2) plot(nextLevelPrice, "Next DCA Level", color=color.new(color.red, 0), style=plot.style_cross) plotchar(numBuys, "Longs Count", "", location=location.top)