
This strategy is based on the Z-Score statistical concept, used to identify statistical deviations of price relative to its local mean. The strategy calculates the Z-Score of closing prices and applies short-term and long-term moving averages to smooth the Z-Score values. Long entry signals are generated when the short-term smoothed Z-Score crosses above the long-term smoothed Z-Score, and exit signals are generated when the short-term smoothed Z-Score crosses below the long-term smoothed Z-Score. The strategy also includes signal spacing control and momentum-based candle filters to reduce noise trading.
The core of this strategy is the calculation and application of Z-Score. Z-Score is a statistical measure that quantifies how far a data point is from the sample mean, measured in standard deviations. In this strategy, the Z-Score calculation formula is: Z = (Close - SMA(Close, N)) / STDEV(Close, N) where N is the user-defined base period.
The strategy execution process is as follows: 1. Calculate the raw Z-Score of the closing price 2. Apply short-term smoothing (SMA) to the raw Z-Score 3. Apply long-term smoothing (SMA) to the raw Z-Score 4. When the short-term smoothed Z-Score crosses above the long-term smoothed Z-Score, if additional conditions are met, open a long position 5. When the short-term smoothed Z-Score crosses below the long-term smoothed Z-Score, if additional conditions are met, close the position
Additional conditions include: - Signal gap: A minimum number of candles must pass between two signals of the same type (entry or exit) - Momentum filter: Entry is prohibited when three or more consecutive bullish candles occur; exit is prohibited when three or more consecutive bearish candles occur
Solutions: - Backtest different market environments to find optimal parameter combinations - Integrate trend filters to reduce or disable trading in strong trending markets - Add additional confirmation indicators such as volume analysis or other technical indicators - Consider using adaptive parameters that automatically adjust based on market volatility
The Momentum-Filtered Smoothed Z-Score Crossover Price Statistical Trading Strategy is a concise trading system based on statistical principles, focused on capturing price deviations and reversions relative to local means. Through smoothing processing, signal spacing control, and momentum filtering, the strategy effectively reduces noise trading and improves signal quality. The strategy is particularly suitable for oscillating markets and financial products with obvious mean-reversion behavior.
However, the strategy also has some limitations, such as dependence on statistical assumptions, parameter sensitivity, and single-factor decision-making. By adding trend recognition, volatility adjustment, multi-timeframe analysis, stop-loss mechanisms, volume confirmation, and multi-factor combinations, the robustness and performance of the strategy can be significantly enhanced.
Overall, this is a strategy framework with solid theoretical foundations, simple implementation, and ease of understanding and extension. It is suitable as a basic component of trading systems or as an educational tool to help traders understand the application of statistics in trading.
/*backtest
start: 2024-06-03 00:00:00
end: 2025-06-02 00:00:00
period: 1d
basePeriod: 1d
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT"}]
*/
//@version=6
strategy("Price Statistical Strategy-Z Score V 1.01", overlay=true)
// === Enable / Disable Z-Score Strategy Block ===
enableZScore = input.bool(true, title="Enable Smoothed Z-Score Strategy", tooltip="When enabled, this block calculates a smoothed Z-Score of the closing price and generates entry/exit signals based on crossover behavior between short-term and long-term smoothed Z-Scores.\n\nRecommended for quick and classic detection of price deviation from mean.\nSensitive to outliers. Best suited for relatively normal-distributed market conditions.")
// === Z-Score Parameters ===
zBaseLength = input.int(3, minval=1, title="Z-Score Base Period")
shortSmooth = input.int(3, title="Short-Term Smoothing")
longSmooth = input.int(5, title="Long-Term Smoothing")
// === Z-Score Calculation Function ===
f_zscore(src, length) =>
mean = ta.sma(src, length)
std_dev = ta.stdev(src, length)
z = (src - mean) / std_dev
z
// === Z-Score Logic ===
zRaw = f_zscore(close, zBaseLength)
zShort = ta.sma(zRaw, shortSmooth)
zLong = ta.sma(zRaw, longSmooth)
// === Minimum gap between identical signals ===
gapBars = input.int(5, minval=1, title="Bars gap between identical signals", tooltip="Minimum number of bars required between two identical signals (entry or exit). Helps reduce signal noise.")
// === Candle-based momentum filters ===
bullish_3bars = close > close[1] and close[1] > close[2] and close[2] > close[3] and close[3] > close[4]
bearish_3bars = close < close[1] and close[1] < close[2] and close[2] < close[3] and close[3] < close[4]
// === Entry and Exit Logic with minimum signal gap and candle momentum filter ===
var int lastEntryBar = na
var int lastExitBar = na
if enableZScore
longCondition = (zShort > zLong)
exitCondition = (zShort < zLong)
if longCondition and (na(lastEntryBar) or bar_index - lastEntryBar > gapBars) and not bullish_3bars
strategy.entry("Z Score", strategy.long)
lastEntryBar := bar_index
if exitCondition and (na(lastExitBar) or bar_index - lastExitBar > gapBars) and not bearish_3bars
strategy.close("Z Score", comment="Z Score")
lastExitBar := bar_index
// === Real-time PnL Table for Last Open Position ===
var table positionTable = table.new(position.bottom_right, 2, 2, border_width=1)
// Header Labels
table.cell(positionTable, 0, 0, "Entry Price", text_color=color.white, bgcolor=color.gray)
table.cell(positionTable, 1, 0, "Unrealized PnL (%)", text_color=color.white, bgcolor=color.gray)
// Values (only when position is open)
isLong = strategy.position_size > 0
entryPrice = strategy.position_avg_price
unrealizedPnL = isLong ? (close - entryPrice) / entryPrice * 100 : na
// Define dynamic text color for PnL
pnlColor = unrealizedPnL > 0 ? color.green : unrealizedPnL < 0 ? color.red : color.gray
// Update Table Content
if isLong
table.cell(positionTable, 0, 1, str.tostring(entryPrice, "#.####"), text_color=color.gray, bgcolor=color.new(color.gray, 90))
table.cell(positionTable, 1, 1, str.tostring(unrealizedPnL, "#.##") + " %", text_color=pnlColor, bgcolor=color.new(pnlColor, 90))
else
table.cell(positionTable, 0, 1, "—", text_color=color.gray, bgcolor=color.new(color.gray, 90))
table.cell(positionTable, 1, 1, "—", text_color=color.gray, bgcolor=color.new(color.gray, 90))