//@version=5 strategy("C - SHORT - MA driven", overlay=true, pyramiding=300, initial_capital=100, default_qty_type=strategy.fixed, commission_type=strategy.commission.percent, commission_value=0.0, process_orders_on_close=true, calc_on_order_fills=true, max_labels_count=500) // Close-only execution model: // - усі ордери виконуються тільки по close поточного бара // - у всіх внутрішніх розрахунках fill-ціна = close // - nextLevelPrice / TP-рівні використовуються лише як умови-тригери // - explicit alert() прибрані; режим TradingView: Order fills only // // Ця версія побудована на стабільній short-версії та додає shared CSV parameter pack, // який можна вставити і в strategy, і в pane-indicator. // ===================== // Inputs // ===================== firstSellQtyCoin_in = input.float(0.09, "First Short (coin qty)", minval=0.000001, step=0.001) minOrderQtyCoin_in = input.float(0.09, "Min order qty (coin)", minval=0.0, step=0.001) useEquityPctBase_in = input.bool(true, "Base order as % of fixed equity") baseOrderPctEq_in = input.float(1.0, "Base order % of fixed equity", minval=0.01, step=0.01) equityForSizingUSDT_in = input.float(300.0, "Equity for % sizing (USDT)", minval=0.01, step=0.01) tpPercent_in = input.float(0.22, "TP % (Whole warehouse)", step=0.01) callbackPercent_in = input.float(0.10, "Callback % (Trailing)", step=0.01) marginCallLimit_in = input.int(244, "Margin Call Limit (max shorts)", minval=1) linearRisePercent_in = input.float(0.16, "Margin Call Rise % (after lvl 5)", step=0.01) autoMerge_in = input.bool(false, "Auto Merge") subSellTPPercent_in = input.float(0.36, "Sub-cover TP % (last lot)", step=0.01) requireCloseBelowFullTP_in = input.bool(true, "Require close <= Full TP for full cover") subCoverCloseConfirmMode_in = input.string("breakeven", "Sub-cover close confirmation", options=["off", "breakeven", "subcover_tp"]) requireCloseAboveDcaLevel_in = input.bool(true, "Require close >= Next DCA level") blockDcaOnTpTouch_in = input.bool(false, "Block DCA on Full TP touch even without close confirm") rise1_in = input.float(0.3, "Nonlinear rise lvl2 %", step=0.1) rise2_in = input.float(0.4, "Nonlinear rise lvl3 %", step=0.1) rise3_in = input.float(0.6, "Nonlinear rise lvl4 %", step=0.1) rise4_in = input.float(0.8, "Nonlinear rise lvl5 %", step=0.1) rise5_in = input.float(0.8, "Nonlinear rise lvl6 %", step=0.1) mult2_in = input.float(1.5, "Multiplier lvl2", step=0.1) mult3_in = input.float(1.0, "Multiplier lvl3", step=0.1) mult4_in = input.float(2.0, "Multiplier lvl4", step=0.1) mult5_in = input.float(3.5, "Multiplier lvl5", step=0.1) maxFillsPerBar_in = input.int(6, "Max DCA fills per bar", minval=1, maxval=20) maxSubSellsPerBar_in = input.int(10, "Max SUB-covers 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(14, "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(true, "Use trend-adaptive short sizing") trendMaTf_in = input.string("W", "Trend MA timeframe") trendMaLen_in = input.int(20, "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 shortness score min %", step=0.1) trendScoreMaxPct_in = input.float(75.0, "Trend shortness score max %", step=0.1) minShortInvestPct_in = input.float(0.5, "Min short investment %", step=0.1) maxShortInvestPct_in = input.float(2.0, "Max short investment %", step=0.1) hardBreakevenDeleveragePct_in = input.float(50.0, "Force breakeven deleverage above short exposure %", step=0.1) // Shared CSV pack with indicator. // Order: // 0 firstSellQtyCoin // 1 minOrderQtyCoin // 2 useEquityPctBase (0/1) // 3 baseOrderPctEq // 4 equityForSizingUSDT // 5 tpPercent // 6 callbackPercent // 7 marginCallLimit // 8 linearRisePercent // 9 autoMerge (0/1) // 10 subSellTPPercent // 11 requireCloseBelowFullTP (0/1) // 12 subCoverCloseConfirmMode (0=off,1=breakeven,2=subcover_tp) // 13 requireCloseAboveDcaLevel (0/1) // 14 blockDcaOnTpTouch (0/1) // 15 rise1 // 16 rise2 // 17 rise3 // 18 rise4 // 19 rise5 // 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" : "subcover_tp" firstSellQtyCoin = f_packFloat(0, firstSellQtyCoin_in) minOrderQtyCoin = f_packFloat(1, minOrderQtyCoin_in) useEquityPctBase = f_packBool(2, useEquityPctBase_in) baseOrderPctEq = f_packFloat(3, baseOrderPctEq_in) equityForSizingUSDT = f_packFloat(4, equityForSizingUSDT_in) tpPercent = f_packFloat(5, tpPercent_in) callbackPercent = f_packFloat(6, callbackPercent_in) marginCallLimit = int(math.round(f_packFloat(7, marginCallLimit_in))) linearRisePercent = f_packFloat(8, linearRisePercent_in) autoMerge = f_packBool(9, autoMerge_in) subSellTPPercent = f_packFloat(10, subSellTPPercent_in) requireCloseBelowFullTP = f_packBool(11, requireCloseBelowFullTP_in) subCoverCloseConfirmMode = f_packMode(12, subCoverCloseConfirmMode_in) requireCloseAboveDcaLevel = f_packBool(13, requireCloseAboveDcaLevel_in) blockDcaOnTpTouch = f_packBool(14, blockDcaOnTpTouch_in) rise1 = f_packFloat(15, rise1_in) rise2 = f_packFloat(16, rise2_in) rise3 = f_packFloat(17, rise3_in) rise4 = f_packFloat(18, rise4_in) rise5 = f_packFloat(19, rise5_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 minShortInvestPct = minShortInvestPct_in maxShortInvestPct = maxShortInvestPct_in hardBreakevenDeleveragePct = hardBreakevenDeleveragePct_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 (rolling 3-minute window, order fills only) // ===================== 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 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 f_recentOrdersIn3Min() < maxOrdersPer3Min 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) trendShortnessPct = math.max(0.0, math.min(100.0, 100.0 * (trendSlopeLongBoundPct - trendSlopeRawPct) / trendSlopeRange)) trendScoreRange = math.max(math.abs(trendScoreMaxPct - trendScoreMinPct), 0.000001) trendSizingFactor = math.max(0.0, math.min(1.0, (trendShortnessPct - trendScoreMinPct) / trendScoreRange)) targetShortInvestPct = useTrendAdaptiveSizing ? (minShortInvestPct + (maxShortInvestPct - minShortInvestPct) * trendSizingFactor) : baseOrderPctEq // ===================== // Helper funcs // ===================== f_getRiseForNextLevel(_numSells) => ns = _numSells + 1 ns == 2 ? rise1 : ns == 3 ? rise2 : ns == 4 ? rise3 : ns == 5 ? rise4 : ns == 6 ? rise5 : linearRisePercent f_getMultForNextLevel(_numSells) => ns = _numSells + 1 ns == 2 ? mult2 : ns == 3 ? mult3 : ns == 4 ? mult4 : ns == 5 ? mult5 : 1.0 f_nextLevel(_lastFillPrice, _numSells) => r = f_getRiseForNextLevel(_numSells) _lastFillPrice * (1.0 + r / 100.0) f_recalcAvg(_proceeds, _sizeAbs) => _sizeAbs > 0 ? _proceeds / _sizeAbs : na f_calcBaseOrderQtyCoin() => sizingPct = useTrendAdaptiveSizing ? targetShortInvestPct : baseOrderPctEq rawQtyCoin = useEquityPctBase ? ((equityForSizingUSDT * sizingPct / 100.0) / close) : firstSellQtyCoin math.max(rawQtyCoin, minOrderQtyCoin) f_lotTag(_n) => _n == 1 ? "ds1" : _n <= 6 ? "dsN" + str.tostring(_n) : "ds" + str.tostring(_n) // ===================== // State // ===================== var float posSizeAbs = 0.0 var float posProceedsUSDT = 0.0 var float avgPrice = na var int numSells = 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) * (array.get(lotPrice, i) - close) acc f_totalPnlPct(_pnlUsdt) => equityForSizingUSDT > 0 ? (_pnlUsdt / equityForSizingUSDT) * 100.0 : na f_fullCoverProfitUSDT(_fillPrice) => float acc = 0.0 int n = array.size(lotQty) if n > 0 for i = 0 to n - 1 acc += array.get(lotQty, i) * (array.get(lotPrice, i) - _fillPrice) acc // Cycle-level counters var int cycleScCounter = 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 trailingMin = 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 barScBlock = "" if barstate.isnew barEvents := "" barHasAction := false barDcaBlock := "" barScBlock := "" f_addEvent(_currEvents, _text) => _nextEvents = _currEvents == "" ? _text : _currEvents + "\n" + _text [_nextEvents, true] // ===================== // HARD RESET AT LIVE START // ===================== if liveStartBar posSizeAbs := 0.0 posProceedsUSDT := 0.0 avgPrice := na numSells := 0 lastFillPrice := na nextLevelPrice := na trailingActive := false trailingMin := na cycleBaseQtyCoin := na cycleScCounter := 0 realizedPnlUSDT := 0.0 minTotalPnlUSDT := 0.0 minTotalPnlPct := 0.0 inLiqZone := false liqZoneCount := 0 liqZoneCurrentMaxTopUp := 0.0 liqZoneTotalTopUp := 0.0 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 posProceedsUSDT := 0.0 avgPrice := na numSells := 0 lastFillPrice := na nextLevelPrice := na trailingActive := false trailingMin := na cycleBaseQtyCoin := na cycleScCounter := 0 array.clear(lotIds) array.clear(lotQty) array.clear(lotPrice) array.clear(lotTags) array.clear(lotUsdt) resetCycle := false if f_canPlaceOrder() cycleBaseQtyCoin := f_calcBaseOrderQtyCoin() sellQty = cycleBaseQtyCoin sellUSDT = sellQty * close lotCounter += 1 id0 = "S" + str.tostring(lotCounter) tag0 = f_lotTag(1) strategy.entry(id0, strategy.short, qty=sellQty, comment="Restart Short_0") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time qty0 = sellQty fill0 = close array.push(lotIds, id0) array.push(lotQty, qty0) array.push(lotPrice, fill0) array.push(lotTags, tag0) array.push(lotUsdt, sellUSDT) posSizeAbs += qty0 posProceedsUSDT += sellUSDT avgPrice := fill0 numSells := 1 lastFillPrice := fill0 nextLevelPrice := f_nextLevel(lastFillPrice, numSells) info = tag0 + " @" + str.tostring(fill0, "#.####") + " (" + str.tostring(sellQty, "#.###") + "q)" restartedThisBar := true [_barEvents, _barHasAction] = f_addEvent(barEvents, "RESTART " + info) 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() sellQty = cycleBaseQtyCoin sellUSDT = sellQty * close lotCounter += 1 id0 = "S" + str.tostring(lotCounter) tag0 = f_lotTag(1) strategy.entry(id0, strategy.short, qty=sellQty, comment="First Short_0") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time qty0 = sellQty fill0 = close array.push(lotIds, id0) array.push(lotQty, qty0) array.push(lotPrice, fill0) array.push(lotTags, tag0) array.push(lotUsdt, sellUSDT) posSizeAbs += qty0 posProceedsUSDT += sellUSDT avgPrice := fill0 numSells := 1 lastFillPrice := fill0 nextLevelPrice := f_nextLevel(lastFillPrice, numSells) info = tag0 + " @" + str.tostring(fill0, "#.####") + " (" + str.tostring(sellQty, "#.###") + "q)" [_barEvents, _barHasAction] = f_addEvent(barEvents, "FIRST " + info) barEvents := _barEvents barHasAction := _barHasAction // ===================== // TP state // ===================== tpPrice = avgPrice * (1.0 - tpPercent / 100.0) tpTouch = liveNow and not na(avgPrice) and triggerLow <= tpPrice fullTPCloseOk = not requireCloseBelowFullTP or close <= tpPrice tpCloseConfirmed = tpTouch and fullTPCloseOk tpBlocksDca = blockDcaOnTpTouch ? tpTouch : tpCloseConfirmed // ===================== // FULL TP (priority) // ===================== if liveNow and tpTouch if callbackPercent > 0 trailingActive := true trailingMin := na(trailingMin) ? triggerLow : math.min(trailingMin, triggerLow) trailStop = trailingMin * (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_fullCoverProfitUSDT(close) fullTPWarehouseCloses += 1 if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time resetCycle := true [_barEvents, _barHasAction] = f_addEvent(barEvents, "FULL COVER (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_fullCoverProfitUSDT(close) fullTPWarehouseCloses += 1 if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time resetCycle := true [_barEvents, _barHasAction] = f_addEvent(barEvents, "FULL COVER @" + str.tostring(close, "#.####")) barEvents := _barEvents barHasAction := _barHasAction // ===================== // DCA shorts // ===================== if liveNow and not tpBlocksDca and posSizeAbs > 0 and not restartedThisBar canSellMore = numSells < marginCallLimit fills = 0 while canSellMore and fills < maxFillsPerBar and not na(nextLevelPrice) and triggerHigh >= nextLevelPrice and (not requireCloseAboveDcaLevel or close >= nextLevelPrice) and f_canPlaceOrder() mult = f_getMultForNextLevel(numSells) if na(cycleBaseQtyCoin) cycleBaseQtyCoin := f_calcBaseOrderQtyCoin() sellQty = cycleBaseQtyCoin * mult sellUSDT = sellQty * close lotCounter += 1 id = "S" + str.tostring(lotCounter) newLevel = numSells + 1 tag = f_lotTag(newLevel) triggerLevel = nextLevelPrice strategy.entry(id, strategy.short, qty=sellQty, comment="DCA Short") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time fillP = close qtyS = sellQty array.push(lotIds, id) array.push(lotQty, qtyS) array.push(lotPrice, fillP) array.push(lotTags, tag) array.push(lotUsdt, sellUSDT) posSizeAbs += qtyS posProceedsUSDT += sellUSDT avgPrice := f_recalcAvg(posProceedsUSDT, posSizeAbs) numSells += 1 lastFillPrice := triggerLevel nextLevelPrice := f_nextLevel(lastFillPrice, numSells) trailingActive := false trailingMin := na dcaLine = tag + " close@" + str.tostring(fillP, "#.####") + " trg@" + str.tostring(triggerLevel, "#.####") + " (" + str.tostring(sellQty, "#.###") + "q)" + " sc-TP≤" + str.tostring(fillP * (1.0 - subSellTPPercent / 100.0), "#.####") barDcaBlock := barDcaBlock == "" ? dcaLine : barDcaBlock + "\n" + dcaLine fills += 1 canSellMore := numSells < marginCallLimit if barDcaBlock != "" [_barEvents, _barHasAction] = f_addEvent(barEvents, barDcaBlock) barEvents := _barEvents barHasAction := _barHasAction // ===================== // SUB-COVER // ===================== if liveNow and not tpBlocksDca and posSizeAbs > 0 and numSells > 5 and not restartedThisBar covered = 0 anyCovered = false while covered < 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) currentShortExposurePct = equityForSizingUSDT > 0 ? ((posSizeAbs * close) / equityForSizingUSDT) * 100.0 : 0.0 forceBreakevenDeleverage = currentShortExposurePct > hardBreakevenDeleveragePct lastLotTP = entryLast * (1.0 - subSellTPPercent / 100.0) sellTouchLevel = forceBreakevenDeleverage ? entryLast : lastLotTP subCoverTouch = triggerLow <= sellTouchLevel subCoverCloseOk = forceBreakevenDeleverage ? (close <= entryLast) : subCoverCloseConfirmMode == "off" ? true : subCoverCloseConfirmMode == "breakeven" ? (close <= entryLast) : (close <= lastLotTP) if subCoverTouch and subCoverCloseOk anyCovered := true cycleScCounter += 1 strategy.close(idLast, comment="Sub-cover last lot") ordersThisBar += 1 currentWindowOrders = f_recentOrdersIn3Min() if currentWindowOrders > maxOrdersPerWindowBT maxOrdersPerWindowBT := currentWindowOrders maxOrdersWindowTime := time if ordersThisBar > maxOrdersPerBarBT maxOrdersPerBarBT := ordersThisBar maxOrdersBarTime := time entryProceedsUSDT = qtyLast * entryLast profitUSDT = qtyLast * (entryLast - close) posSizeAbs -= qtyLast posProceedsUSDT -= entryProceedsUSDT realizedPnlUSDT += profitUSDT if autoMerge posProceedsUSDT -= profitUSDT // залишено без зміни, як у базовій short-версії avgPrice := f_recalcAvg(posProceedsUSDT, posSizeAbs) array.pop(lotIds) array.pop(lotQty) array.pop(lotPrice) array.pop(lotTags) array.pop(lotUsdt) numSells := math.max(numSells - 1, 0) covered += 1 scLine = "sc" + str.tostring(cycleScCounter) + " ← " + tagLast + " @" + str.tostring(entryLast, "#.####") + " (" + str.tostring(qtyLast, "#.###") + "q)" + " TP≤" + str.tostring(sellTouchLevel, "#.####") + " fill @" + str.tostring(close, "#.####") barScBlock := barScBlock == "" ? scLine : barScBlock + "\n" + scLine if array.size(lotIds) == 0 or posSizeAbs <= 0 resetCycle := true break else break if barScBlock != "" [_barEvents, _barHasAction] = f_addEvent(barEvents, barScBlock) barEvents := _barEvents barHasAction := _barHasAction if anyCovered and not resetCycle and array.size(lotPrice) > 0 lastFillPrice := array.get(lotPrice, array.size(lotPrice) - 1) nextLevelPrice := f_nextLevel(lastFillPrice, numSells) // PNL / liquidation state unrealizedPnlUSDT = f_unrealizedPnlUSDT() totalPnlUSDT = realizedPnlUSDT + unrealizedPnlUSDT totalPnlPct = f_totalPnlPct(totalPnlUSDT) 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 COVER — окремий жовтий лейбл if barstate.isconfirmed and barHasAction and str.contains(barEvents, "FULL COVER") label.new( bar_index, close, "★ FULL COVER\n" + barEvents, style = label.style_label_up, color = color.yellow, textcolor = color.black, 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=" + subCoverCloseConfirmMode 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 sum USDT: " + str.tostring(posNotionalUSDT, "#.##") + "\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, "#.##") + "\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(numSells, "Shorts Count", "", location=location.top)