Research briefing

C05793 / sizing-D000000

Campaign campaign-v6-20260901-0003-ca5dc3d7e0e6 C05793/sizing-D000000

Archived Exploratory subjectExploratory

Total returnTWR
330.43%
AnnualizedTWR
199.28%
Max DDObserved
49.71%
SharpeNet
1.85
K-ratioNet
0.70
TradesClosed
410

What is VESTROS?

hypeperp-15m-c05793-extended-history-v1

Owner-selected archive of C05793/sizing-D000000 from campaign campaign-v6-20260901-0003-ca5dc3d7e0e6.

Assessment: historically-promising-limited-sample. Warnings: drawdown-out-of-character.

Archiving preserves the strategy and its research findings. It does not change campaign qualification.

OOS update

Refresh through the latest confirmed history

Run the same archived design through the start of the current UTC day and publish its OOS continuation. Only confirmed history is included.

vst system backtest hypeperp-15m-c05793-extended-history-v1 --to "$(date -u +%Y-%m-%dT00:00:00Z)" --publish-oos

Research evidence only. External execution is not authorized.

system.pine

C05793: HYPEUSDC 15m Pine Script v6

Archive defaults: HYPEUSDC 15m decisions 15m execution. Uses the current chart symbol and timeframe.

Regenerated from the installed original archive. TradingView verification on 46,892 identical original candles, including seed history, matched trade decisions and quantities, with equity within 0.0000001. The Archive rules table and native fill markers use loaded chart candles with the archive calculation and account rules. Use the archived execution timeframe and the same candle history from the same origin to compare results. Changed charts or inputs are fresh exploratory runs. Optional Strategy Tester orders approximate sizing and percentage slippage; broker settings are separate from the Archive account inputs.

//@version=6// SPDX-License-Identifier: MPL-2.0// RSI calculation derived from TradingView, Inc., RSI Divergence Indicator.// https://www.tradingview.com/support/solutions/43000589127-rsi-divergence-indicator/// Regenerated from the public archive's installed original calculation variant.// The Archive rules table uses actual chart candles, next-open sizing, quantity// steps, 0.06% fees and 0.02% fill-price slippage. History must match from origin// to compare with the archive. Changing the chart or inputs is a fresh run.// Strategy Tester is an optional broker-emulator approximation. Percentage// slippage and open-priced rebalance quantities cannot be expressed there.strategy("VESTROS C05793: original archive rules", overlay = true,     initial_capital = 10000, default_qty_type = strategy.percent_of_equity,     default_qty_value = 100, commission_type = strategy.commission.percent,     commission_value = 0.08, slippage = 0, pyramiding = 100,     margin_long = 0, margin_short = 0, process_orders_on_close = false,     calc_on_every_tick = false, calc_on_order_fills = false, max_bars_back = 5000)// Arithmetic follows the installed archive's original calculation runtime.// math.sign avoids Pine's rounded float comparisons at strict boundaries.f_gt(float a, float b) =>    math.sign(a - b) == 1f_lt(float a, float b) =>    math.sign(a - b) == -1f_eq(float a, float b) =>    math.sign(a - b) == 0f_ge(float a, float b) =>    not na(a) and not na(b) and math.sign(a - b) >= 0f_le(float a, float b) =>    not na(a) and not na(b) and math.sign(a - b) <= 0f_number(bool value) =>    value ? 1.0 : 0.0type Samples    array<float> valuesf_samples() =>    Samples.new(array.new<float>())method add(Samples self, float value, int capacity = 82) =>    self.values.push(value)    if self.values.size() > capacity        self.values.shift()method lag(Samples self, int offset = 0) =>    self.values.size() > offset ? self.values.get(self.values.size() - 1 - offset) : na// Strict, contiguous, oldest-to-newest windows preserve initialization and// floating-point operation order. Missing values propagate.method stats(Samples self, int length, int offset = 0) =>    float average = na    float deviation = na    if self.values.size() >= length + offset        float total = 0        for i = length - 1 to 0            total += self.lag(i + offset)        average := total / length        float squares = 0        for i = length - 1 to 0            float delta = self.lag(i + offset) - average            squares += delta * delta        deviation := math.sqrt(squares / length)    [average, deviation]method zscore(Samples self, int length) =>    [average, deviation] = self.stats(length)    (self.lag() - average) / deviationmethod volatility(Samples self, int length, int offset = 1) =>    [average, deviation] = self.stats(length, offset)    deviationmethod rangePosition(Samples self, int length) =>    float minimum = self.lag(1)    float maximum = self.lag(1)    for i = 2 to length        minimum := math.min(minimum, self.lag(i))        maximum := math.max(maximum, self.lag(i))    2 * (self.lag() - minimum) / (maximum - minimum) - 1method rank(Samples self, int length) =>    float current = self.lag()    bool complete = not na(current)    int less = 0    int equal = 0    for i = 1 to length        float prior = self.lag(i)        complete := complete and not na(prior)        less += f_lt(prior, current) ? 1 : 0        equal += f_eq(prior, current) ? 1 : 0    complete ? 2.0 * (less + equal / 2.0) / length - 1 : namethod persistence(Samples self, int length, float threshold) =>    bool complete = true    bool passed = true    for i = 0 to length - 1        float value = self.lag(i)        complete := complete and not na(value)        passed := passed and (threshold >= 0 ? f_gt(value, threshold) : f_lt(value, threshold))    complete ? f_number(passed) : naf_relative(Samples left, Samples right, int length) =>    (left.lag() - left.lag(length)) / math.abs(left.lag(length)) - (right.lag() - right.lag(length)) / math.abs(right.lag(length))f_cross(Samples left, Samples right, bool above) =>    bool complete = not na(left.lag()) and not na(right.lag()) and not na(left.lag(1)) and not na(right.lag(1))    complete ? f_number(above ? f_gt(left.lag(), right.lag()) and f_le(left.lag(1), right.lag(1)) : f_lt(left.lag(), right.lag()) and f_ge(left.lag(1), right.lag(1))) : naf_compare(float left, float right, string comparison) =>    not na(left) and not na(right) ? f_number(comparison == "gt" ? f_gt(left, right) : comparison == "lt" ? f_lt(left, right) : comparison == "ge" ? f_ge(left, right) : f_le(left, right)) : naf_votes(array<float> values, int required) =>    int passed = 0    int unknown = 0    for value in values        passed += value == 1 ? 1 : 0        unknown += na(value) ? 1 : 0    passed >= required ? 1.0 : passed + unknown < required ? 0.0 : namethod riskScale(Samples self) =>    Samples returns = f_samples()    bool positive = true    for i = 80 to 1        float previous = self.lag(i)        float current = self.lag(i - 1)        positive := positive and f_gt(previous, 0) and f_gt(current, 0)        returns.add(current / previous - 1)    [slowMean, slow] = returns.stats(60, 20)    [fastMean, fast] = returns.stats(20)    positive and f_gt(slow, 0) and f_gt(fast, 0) ? math.min(1, math.max(0.25, slow / fast)) : naf_histories(int count) =>    array<Samples> result = array.new<Samples>()    for i = 1 to count        result.push(f_samples())    result// The retained TradingView RSI-divergence port used alpha-form RMA and strict// right-hand pivots. Keep that original variant for these installed archives.type RsiState    int length    int leftBars    int rightBars    float lastClose = na    float gain = na    float loss = na    float gainSum = 0    float lossSum = 0    int seedCount = 0    Samples oscillators    Samples highs    Samples lows    float oscillator = na    float latestLowOscillator = na    float latestLowPrice = na    float latestHighOscillator = na    float latestHighPrice = na    float previousLowPivotOscillator = na    float previousLowPivotPrice = na    float previousHighPivotOscillator = na    float previousHighPivotPrice = na    bool precedingLowFound = false    int lowPivotBarsSincePrevious = naf_rsi(int length, int leftBars, int rightBars) =>    RsiState.new(length, leftBars, rightBars, oscillators = f_samples(), highs = f_samples(), lows = f_samples())method calculate(RsiState self, float highPrice, float lowPrice, float closePrice) =>    if not na(self.lastClose)        float change = closePrice - self.lastClose        float gain = math.max(change, 0)        float loss = math.max(-change, 0)        if na(self.gain)            self.gainSum += gain            self.lossSum += loss            self.seedCount += 1            if self.seedCount == self.length                self.gain := self.gainSum / self.length                self.loss := self.lossSum / self.length        else            float alpha = 1.0 / self.length            self.gain := alpha * gain + (1 - alpha) * self.gain            self.loss := alpha * loss + (1 - alpha) * self.loss        self.oscillator := na(self.gain) or na(self.loss) ? na : f_eq(self.loss, 0) ? (f_eq(self.gain, 0) ? na : 100) : f_eq(self.gain, 0) ? 0 : 100 - 100 / (1 + self.gain / self.loss)    self.lastClose := closePrice    int width = self.leftBars + self.rightBars + 1    self.oscillators.add(self.oscillator, width)    self.highs.add(highPrice, width)    self.lows.add(lowPrice, width)    bool lowFound = false    bool highFound = false    if self.oscillators.values.size() == width        float candidate = self.oscillators.lag(self.rightBars)        lowFound := not na(candidate)        highFound := not na(candidate)        for i = 0 to width - 1            float neighbour = self.oscillators.lag(i)            if i != self.rightBars and not na(neighbour)                lowFound := lowFound and (i < self.rightBars ? f_lt(candidate, neighbour) : f_le(candidate, neighbour))                highFound := highFound and (i < self.rightBars ? f_gt(candidate, neighbour) : f_ge(candidate, neighbour))        if lowFound            self.previousLowPivotOscillator := self.latestLowOscillator            self.previousLowPivotPrice := self.latestLowPrice            self.latestLowOscillator := candidate            self.latestLowPrice := self.lows.lag(self.rightBars)        if highFound            self.previousHighPivotOscillator := self.latestHighOscillator            self.previousHighPivotPrice := self.latestHighPrice            self.latestHighOscillator := candidate            self.latestHighPrice := self.highs.lag(self.rightBars)    self.lowPivotBarsSincePrevious := self.precedingLowFound ? 0 : na(self.lowPivotBarsSincePrevious) ? na : self.lowPivotBarsSincePrevious + 1    self.precedingLowFound := lowFound// Candle accounting is separate from TradingView's broker emulator because// the archive sizes at the execution open and applies percentage slippage.type Holding    int steps = 0    float average = 0    bool pending = false    int targetDirection = 0    float allocation = 1    float executedDelta = 0    float executedPrice = na    float markedPrice = na    float referencePrice = na    float plannedPrice = 0type Account    float initial    float wallet    float equity    float peak    float drawdown = 0    float fees = 0    float slippage = 0    int fills = 0    int closed = 0    array<Holding> holdings    string mode = "combination"f_account(float capital, int routes = 1, string mode = "combination") =>    array<Holding> holdings = array.new<Holding>()    for i = 1 to routes        holdings.push(Holding.new(allocation = 1.0 / routes))    Account.new(capital, capital, capital, capital, holdings = holdings, mode = mode)method valueAt(Account self, float price, float quantityStep) =>    float value = self.wallet    for holding in self.holdings        value += holding.steps * quantityStep * (price - holding.average)    valuemethod observe(Account self, float quantityStep) =>    self.equity := self.wallet    for holding in self.holdings        if holding.steps != 0            self.equity += holding.steps * quantityStep * (holding.markedPrice - holding.average)    self.peak := math.max(self.peak, self.equity)    self.drawdown := math.max(self.drawdown, (self.peak - self.equity) / self.peak)method mark(Account self, float price, float quantityStep) =>    for holding in self.holdings        holding.markedPrice := price    self.observe(quantityStep)method execute(Account self, float openPrice, float quantityStep, float feeRate, float slipRate) =>    // Simultaneous route targets use the same equity marked before their fills.    float sizingEquity = self.valueAt(openPrice, quantityStep)    for holding in self.holdings        holding.executedDelta := 0        holding.executedPrice := na        if holding.pending            float sizingPrice = openPrice            float sizingNotional = math.max(sizingEquity, 0) * holding.allocation            if self.mode == "managed"                // Managed v2 reserves planned entry exposure at the signal close.                // The next route sees the account after earlier route fills.                float reserved = 0                for other in self.holdings                    reserved += math.abs(other.steps) * quantityStep * other.plannedPrice                sizingPrice := holding.referencePrice                sizingNotional := math.min(math.max(self.equity, 0) * holding.allocation, math.max(self.equity - reserved, 0))            int target = holding.targetDirection == 0 ? 0 : holding.targetDirection * int(math.floor(sizingNotional / (sizingPrice * quantityStep)))            int before = holding.steps            int deltaSteps = target - before            if deltaSteps != 0                float delta = deltaSteps * quantityStep                float fill = openPrice * (deltaSteps > 0 ? 1 + slipRate : 1 - slipRate)                float fee = math.abs(delta) * fill * feeRate                self.wallet -= fee                self.fees += fee                self.slippage += math.abs(delta) * math.abs(fill - openPrice)                self.fills += 1                holding.executedDelta := delta                holding.executedPrice := fill                if before == 0 or math.sign(before) == math.sign(deltaSteps)                    holding.average := (math.abs(before) * quantityStep * holding.average + math.abs(delta) * fill) / (math.abs(target) * quantityStep)                else                    int closing = math.min(math.abs(before), math.abs(deltaSteps))                    self.wallet += closing * quantityStep * math.sign(before) * (fill - holding.average)                    if target == 0 or math.sign(target) != math.sign(before)                        self.closed += 1                        holding.average := target == 0 ? 0 : fill                holding.steps := target                holding.markedPrice := fill                if before == 0 or math.sign(before) != math.sign(target)                    holding.plannedPrice := target == 0 ? 0 : holding.referencePrice                if self.mode == "combination"                    self.mark(fill, quantityStep)                else if self.mode == "managed"                    self.observe(quantityStep)            holding.pending := false    if self.mode == "open"        self.mark(openPrice, quantityStep)type Member    bool active = false    int enteredAt = na    int heldBars = 0    bool changed = false    float entryPrice = na    float highestClose = na    float lowestClose = na    bool pendingReference = falsemethod fillReference(Member self, float openPrice, float slipRate, int direction) =>    if self.pendingReference        self.entryPrice := openPrice * (direction == 1 ? 1 + slipRate : 1 - slipRate)        self.pendingReference := falsemethod decide(Member self, bool entry, bool exit, int closeAt) =>    bool before = self.active    if self.active        self.heldBars += 1        if exit            self.active := false            self.enteredAt := na            self.heldBars := 0            self.highestClose := na            self.lowestClose := na            self.entryPrice := na    else if entry        self.active := true        self.enteredAt := closeAt        self.heldBars := 0        self.pendingReference := true    self.changed := self.active != beforetype Incumbent    int member = 0    int inactive = 0    bool desired = false    bool changed = falsemethod combine(Incumbent self, Member a, Member b) =>    bool event = a.changed or b.changed    bool before = self.desired    self.changed := false    if event or (self.desired and self.member != 0 and not a.active and not b.active)        if a.active or b.active            bool keep = (self.member == 1 and a.active) or (self.member == 2 and b.active)            if not keep                self.member := a.active and b.active ? (a.enteredAt <= b.enteredAt ? 1 : 2) : a.active ? 1 : 2            self.inactive := 0            self.desired := true        else if self.desired and self.member != 0            self.inactive += 1            if self.inactive >= 2                self.member := 0                self.desired := false        else            self.member := 0            self.inactive := 0            self.desired := false        self.changed := event or self.desired != beforetype Program0    RsiState calculation    array<Samples> history    bool entry = false    bool exit = falsemethod calculate(Program0 self, float highPrice, float lowPrice, float closePrice) =>    self.calculation.calculate(highPrice, lowPrice, closePrice)    float n0 = self.calculation.lowPivotBarsSincePrevious    self.history.get(0).add(n0)    float n1 = self.calculation.oscillator    self.history.get(1).add(n1)    float n2 = self.calculation.previousHighPivotOscillator    self.history.get(2).add(n2)    float n3 = self.calculation.previousHighPivotPrice    self.history.get(3).add(n3)    float n4 = self.calculation.previousLowPivotOscillator    self.history.get(4).add(n4)    float n5 = 0.0    self.history.get(5).add(n5)    float n6 = -1.0    self.history.get(6).add(n6)    float n7 = 0.0    self.history.get(7).add(n7)    float n8 = 1.0    self.history.get(8).add(n8)    float n9 = n1 - self.history.get(1).lag(1)    self.history.get(9).add(n9)    float n10 = n2 - self.history.get(2).lag(1)    self.history.get(10).add(n10)    float n11 = self.history.get(0).rangePosition(20)    self.history.get(11).add(n11)    float n12 = f_relative(self.history.get(3), self.history.get(4), 60)    self.history.get(12).add(n12)    float n13 = na(n9) or na(n10) ? na : f_number(f_gt(n9, 0) and f_lt(n10, 0) or f_lt(n9, 0) and f_gt(n10, 0))    self.history.get(13).add(n13)    float n14 = f_compare(n9, n5, "lt")    self.history.get(14).add(n14)    float n15 = f_compare(n11, n6, "lt")    self.history.get(15).add(n15)    float n16 = f_compare(n11, n8, "gt")    self.history.get(16).add(n16)    float n17 = f_compare(n12, n7, "lt")    self.history.get(17).add(n17)    float n18 = f_votes(array.from(n13, n14), 2)    self.history.get(18).add(n18)    float n19 = f_votes(array.from(n18, n16, n17), 2)    self.history.get(19).add(n19)    self.entry := n19 == 1    self.exit := n15 == 1method advance(Account account, Program0 a, Member aMember, float openPrice, float highPrice, float lowPrice, float closePrice, int closeAt, bool scored, bool mayDecide, float quantityStep, float feeRate, float slipRate) =>    Holding holding = account.holdings.get(0)    if scored        account.execute(openPrice, quantityStep, feeRate, slipRate)    a.calculate(highPrice, lowPrice, closePrice)    if mayDecide        aMember.decide(a.entry, a.exit, closeAt)    if mayDecide and aMember.changed        holding.pending := true        holding.targetDirection := aMember.active ? 1 : 0    if scored        account.mark(closePrice, quantityStep)useWindow = input.bool(true, "Use archive date window", group = "Replay")startAt = input.time(1746144900000, "First scored open", group = "Replay")endAt = input.time(1788220800000, "End (exclusive)", group = "Replay")initialCapital = input.float(10000, "Initial equity", minval = 1, group = "Archive account")quantityStep = input.float(0.01, "Quantity step", minval = 0.000001, group = "Archive account")feeRate = input.float(0.06, "Fee (%)", minval = 0, group = "Archive account") / 100slipRate = input.float(0.02, "Slippage (%)", minval = 0, group = "Archive account") / 100showOrders = input.bool(false, "Show approximate broker orders", group = "Display")showNativeFills = input.bool(true, "Show native fill markers", group = "Display")scored = not useWindow or time >= startAt and time < endAtmayDecide = scored and (not useWindow or time_close < endAt)var Account account = f_account(initialCapital, 1, "open")var int origin = timevar int scoredCount = 0aLength = input.int(7, "RSI length", minval = 1, maxval = 300, group = "Member 1")aLeft = input.int(5, "Left pivot bars", minval = 1, maxval = 300, group = "Member 1")aRight = input.int(5, "Right pivot bars", minval = 1, maxval = 300, group = "Member 1")var Program0 a = Program0.new(f_rsi(aLength, aLeft, aRight), f_histories(20))var Member aMember = Member.new()if barstate.isconfirmed    account.advance(a, aMember, open, high, low, close, time_close, scored, mayDecide, quantityStep, feeRate, slipRate)    Holding holding = account.holdings.get(0)    if scored        scoredCount += 1    if showOrders and mayDecide and holding.pending        float target = holding.targetDirection == 0 ? 0 : math.floor(math.max(account.equity, 0) / (close * quantityStep)) * quantityStep        float delta = target - strategy.position_size        if f_gt(delta, 0)            strategy.order("Increase", strategy.long, qty = delta)        else if f_lt(delta, 0)            strategy.order("Decrease", strategy.short, qty = -delta)var table summary = table.new(position.top_right, 2, 8, bgcolor = color.new(color.black, 10))if barstate.islast    table.cell(summary, 0, 0, "Archive rules", text_color = color.white)    table.cell(summary, 1, 0, "Loaded chart candles", text_color = color.white)    table.cell(summary, 0, 1, "Equity", text_color = color.white)    table.cell(summary, 1, 1, str.tostring(account.equity, "#.########"), text_color = color.white)    table.cell(summary, 0, 2, "Net return", text_color = color.white)    table.cell(summary, 1, 2, str.tostring((account.equity / initialCapital - 1) * 100, "#.########") + "%", text_color = color.white)    table.cell(summary, 0, 3, "Maximum drawdown", text_color = color.white)    table.cell(summary, 1, 3, str.tostring(account.drawdown * 100, "#.########") + "%", text_color = color.white)    table.cell(summary, 0, 4, "Closed trades / fills", text_color = color.white)    table.cell(summary, 1, 4, str.tostring(account.closed) + " / " + str.tostring(account.fills), text_color = color.white)    table.cell(summary, 0, 5, "Fees", text_color = color.white)    table.cell(summary, 1, 5, str.tostring(account.fees, "#.########"), text_color = color.white)    table.cell(summary, 0, 6, "Calculation origin", text_color = color.white)    table.cell(summary, 1, 6, str.format_time(origin, "yyyy-MM-dd HH:mm", "UTC"), text_color = color.white)    table.cell(summary, 0, 7, "Scored candles", text_color = color.white)    table.cell(summary, 1, 7, str.tostring(scoredCount), text_color = color.white)plot(account.equity, "native_equity", display = display.data_window)plot(account.holdings.get(0).steps * quantityStep, "native_quantity", display = display.data_window)plot(account.holdings.get(0).executedDelta, "native_delta", display = display.data_window)plot(account.fees, "native_fees", display = display.data_window)plot(account.drawdown, "native_drawdown", display = display.data_window)plot(account.fills, "native_fills", display = display.data_window)plot(account.closed, "native_closed", display = display.data_window)plot(aMember.active ? 1 : 0, "a_active", display = display.data_window)plotshape(showNativeFills and scored and barstate.isconfirmed and f_gt(account.holdings.get(0).executedDelta, 0), "Native buy fill", shape.triangleup, location.belowbar, color.teal, size = size.tiny)plotshape(showNativeFills and scored and barstate.isconfirmed and f_lt(account.holdings.get(0).executedDelta, 0), "Native sell fill", shape.triangledown, location.abovebar, color.red, size = size.tiny)plot(a.history.get(0).lag(), "a_feature_001", display = display.data_window)plot(a.history.get(1).lag(), "a_feature_002", display = display.data_window)plot(a.history.get(2).lag(), "a_feature_003", display = display.data_window)plot(a.history.get(3).lag(), "a_feature_004", display = display.data_window)plot(a.history.get(4).lag(), "a_feature_005", display = display.data_window)plot(a.history.get(5).lag(), "a_feature_006", display = display.data_window)plot(a.history.get(6).lag(), "a_feature_007", display = display.data_window)plot(a.history.get(7).lag(), "a_feature_008", display = display.data_window)plot(a.history.get(8).lag(), "a_feature_009", display = display.data_window)plot(a.history.get(9).lag(), "a_feature_010", display = display.data_window)plot(a.history.get(10).lag(), "a_feature_011", display = display.data_window)plot(a.history.get(11).lag(), "a_feature_012", display = display.data_window)plot(a.history.get(12).lag(), "a_feature_013", display = display.data_window)plot(a.history.get(13).lag(), "a_feature_014", display = display.data_window)plot(a.history.get(14).lag(), "a_feature_015", display = display.data_window)plot(a.history.get(15).lag(), "a_feature_016", display = display.data_window)plot(a.history.get(16).lag(), "a_feature_017", display = display.data_window)plot(a.history.get(17).lag(), "a_feature_018", display = display.data_window)plot(a.history.get(18).lag(), "a_feature_019", display = display.data_window)plot(a.history.get(19).lag(), "a_feature_020", display = display.data_window)