// This Pine Script® code is licensed under Apache-2.0
// SPDX-License-Identifier: Apache-2.0
// © PineForge contributors 2026
//
// VCP probe 04 — volume Z-score anomaly
//
// Purpose: isolate the volume-anomaly half of the VCP integration probe
// family. That family computes
// ``volZScore = (volume - volMA) / volStd`` where ``volMA = ta.sma(volume, 20)``
// and ``volStd = ta.stdev(volume, 20)``. ``volAnomaly = math.abs(volZScore) > 2``
// then feeds the volume confluence as a ``+1`` bonus AND multiplies the
// smart-money score by 1.5. A small drift in ``ta.stdev`` (e.g. sample-vs-
// population denominator, or order of summation) shifts the |z|>2 threshold
// crossings and changes WHICH bars trip the anomaly bonus.
//
// This probe routes a trivial entry/exit through ONLY the volume z-score
// anomaly. Any TV/engine drift here is attributable to ``ta.sma(volume, 20)``
// + ``ta.stdev(volume, 20)`` precision at a length matching VCP, NOT to
// VCP's other filters.
//
// Trade shape: long-only, pyramiding=1. Enter long on a high-volume
// bullish anomaly (|z|>2 on an up-bar, ``close > open``). Close the
// long on a high-volume bearish anomaly (|z|>2 on a down-bar).
//
// TV setup: 15m chart, ETH-USDT-USDT, same window as
// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv
// in this folder.
//@version=6
strategy("VCP probe 04 - vol zscore anomaly", shorttitle="VCP_p04",
overlay=true, initial_capital=1000000,
commission_type=strategy.commission.percent, commission_value=0,
slippage=0, default_qty_type=strategy.fixed, default_qty_value=1,
pyramiding=1, process_orders_on_close=false)
// ---- inputs (mirror VCP defaults) ----------------------------------
int i_vol_ma = input.int(20, "Volume MA Length", minval=5, maxval=50)
// ---- volume Z-score — IDENTICAL formula to VCP ---------------------
// Note: VCP hardcodes the stdev length to 20 (line 312). The MA length
// is also 20 by default. Both passes use the same lookback so ``volMA``
// and ``volStd`` are aligned.
float volMA = ta.sma(volume, i_vol_ma)
float volStd = ta.stdev(volume, 20)
float volZ = (volume - volMA) / volStd
bool volAnomaly = math.abs(volZ) > 2.0
// ---- trivial entry / exit ------------------------------------------
if volAnomaly and close > open and strategy.position_size == 0
strategy.entry("L", strategy.long, qty=1, comment="vol-z anomaly bull")
if volAnomaly and close < open and strategy.position_size > 0
strategy.close("L", comment="vol-z anomaly bear exit")