Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Improvement Suggestions

Here is a roadmap for evolving the KNN Momentum Indicator into a professional-grade trading system, structured across three additive levels of enhancement.

Level 1: Parameter Optimization & Dynamic Adaptability

The current script, while conceptually advanced, relies on static, “hard-coded” parameters for its core logic and risk management (or lack thereof). This makes it brittle and prone to curve-fitting. Level 1 upgrades focus on introducing dynamic adaptability, allowing the system to breathe with the market’s current volatility and behavior.

Technical Logic & Suggested Upgrades

  1. Implement an ATR-Based Risk Management Module: A signal is meaningless without a predefined exit strategy. The most critical first step is to integrate a non-discretionary risk framework.

    • Logic: Upon a signal, calculate the Average True Range (ATR) over a lookback period (e.g., 14 periods). The stop-loss is placed at a multiple of this ATR below the entry price for a long (or above for a short). The take-profit is set at a different multiple, establishing a fixed Risk/Reward ratio.

    • Pine Script Implementation:

      // --- INPUTS FOR RISK MANAGEMENT ---
      atr_len = input.int(14, "ATR Period", group="🛡️ Risk Management")
      stop_loss_mult = input.float(1.5, "Stop Loss ATR Multiple", group="🛡️ Risk Management")
      take_profit_mult = input.float(3.0, "Take Profit ATR Multiple", group="🛡️ Risk Management")
      
      // --- STRATEGY CONVERSION ---
      // Convert the indicator to a strategy
      // strategy("KNN Professional System", overlay=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
      
      // --- DYNAMIC EXIT LOGIC ---
      float atr_val = ta.atr(atr_len)
      
      if (long_signal)
          strategy.entry("KNN Long", strategy.long)
          strategy.exit("Exit Long", "KNN Long", stop=close - atr_val * stop_loss_mult, limit=close + atr_val * take_profit_mult)
      
      if (short_signal)
          strategy.entry("KNN Short", strategy.short)
          strategy.exit("Exit Short", "KNN Short", stop=close + atr_val * stop_loss_mult, limit=close - atr_val * take_profit_mult)
  2. Introduce an Adaptive Prediction Threshold: The static prob_threshold of 0.9 is arbitrary. In a low-volatility environment, the model may never reach this confidence level, leading to zero trades. In a highly directional market, it might be too low. The threshold should adapt to the model’s own recent output.

    • Logic: Instead of a fixed value, calculate the Nth percentile (e.g., 90th) of the prob_up and prob_down values over the window_size. A signal is triggered when the current probability crosses this dynamic, self-adjusting threshold.

    • Pine Script Implementation:

      // --- ADAPTIVE THRESHOLD LOGIC ---
      adaptive_percentile = input.float(90.0, "Adaptive Threshold Percentile", group=group_ml)
      
      // Calculate historical distribution of probabilities
      float prob_up_history = prob_up[1]
      float prob_down_history = prob_down[1]
      
      // Calculate the percentile over the learning window
      float dynamic_threshold_up = ta.percentile_linear_interpolation(prob_up_history, window_size, adaptive_percentile)
      float dynamic_threshold_down = ta.percentile_linear_interpolation(prob_down_history, window_size, adaptive_percentile)
      
      // Update signal generation
      bool raw_long_signal = ta.crossover(prob_up, dynamic_threshold_up)
      bool raw_short_signal = ta.crossover(prob_down, dynamic_threshold_down)

Quantitative Benefit


Level 2: Secondary Confluence & Noise Filtration

With a dynamic foundation in place, Level 2 focuses on improving the quality of signals by eliminating low-probability setups. The goal is to increase precision by adding secondary filters that confirm the conviction behind a KNN-identified pattern.

Technical Logic & Suggested Upgrades

  1. Implement a Volume-Weighted Confirmation Filter: A price move without volume is a low-conviction move. The KNN model, being price-based, is blind to market participation. This filter ensures that a signal is backed by institutional interest or broad consensus.

    • Logic: A signal is only considered valid if the volume on the signal bar is greater than a moving average of volume (e.g., volume > ta.sma(volume, 20)). This simple check filters out signals occurring in illiquid, “choppy” environments where follow-through is unlikely.

    • Pine Script Implementation:

      // --- INPUTS FOR FILTERS ---
      use_volume_filter = input.bool(true, "Enable Volume Filter", group="🔍 Filters")
      volume_filter_len = input.int(20, "Volume MA Period", group="🔍 Filters")
      
      // --- FILTER LOGIC ---
      bool volume_confirmed = volume > ta.sma(volume, volume_filter_len)
      
      // --- APPLY TO SIGNAL ---
      if (raw_long_signal and last_dir <= 0 and (volume_confirmed or not use_volume_filter))
          long_signal := true
          last_dir := 1
      
      if (raw_short_signal and last_dir >= 0 and (volume_confirmed or not use_volume_filter))
          short_signal := true
          last_dir := -1
  2. Integrate a Higher-Timeframe (HTF) Directional Bias: A momentum signal on a 15-minute chart is significantly more likely to succeed if it aligns with the prevailing trend on the 4-hour or Daily chart. This filter prevents fighting the “primary tide.”

    • Logic: Use Pine Script’s request.security() function to fetch the state of a long-term moving average (e.g., 200 EMA) from a higher timeframe. A long signal is only permitted if the price on the HTF is above its 200 EMA. A short signal is only permitted if the price is below it.

    • Pine Script Implementation:

      // --- INPUTS FOR FILTERS ---
      use_htf_filter = input.bool(true, "Enable HTF Trend Filter", group="🔍 Filters")
      htf_timeframe = input.timeframe("240", "Higher Timeframe", group="🔍 Filters")
      htf_ema_len = input.int(50, "HTF EMA Period", group="🔍 Filters")
      
      // --- HTF LOGIC ---
      float htf_ema = request.security(syminfo.tickerid, htf_timeframe, ta.ema(close, htf_ema_len))
      bool htf_is_bullish = close > htf_ema
      bool htf_is_bearish = close < htf_ema
      
      // --- APPLY TO SIGNAL (EXAMPLE FOR LONG) ---
      if (raw_long_signal and last_dir <= 0 and (volume_confirmed or not use_volume_filter) and (htf_is_bullish or not use_htf_filter))
          long_signal := true
          last_dir := 1
      // ... similar logic for short signal

Quantitative Benefit


Level 3: Structural Architecture & Regime Detection

Level 3 moves beyond simple filters to fundamentally alter the strategy’s engine. The goal is to make the system “market-aware,” enabling it to adapt its core behavior based on the prevailing market cycle or “regime.” This is the hallmark of a truly professional, all-weather system.

Technical Logic & Suggested Upgrades

  1. Implement a Market Regime Filter (e.g., Gaussian Filter or Hurst Exponent): The KNN momentum model is designed for trending or transitional markets. It will perform poorly in sideways, mean-reverting chop. A regime filter acts as a master switch, activating the strategy only when conditions are favorable.

    • Logic: A low-lag filter like a 2-pole Gaussian Filter can be used to model the dominant market cycle. The first derivative (slope) of this filter indicates the market’s mode. A steep, positive/negative slope signifies a strong trend (enable the KNN strategy). A near-zero slope signifies a ranging market (disable the KNN strategy).

    • Pine Script Implementation (Conceptual):

      // --- REGIME FILTER LOGIC (GAUSSIAN) ---
      // [Function to calculate a 2-pole Gaussian Filter would be defined here]
      // alpha = 1 - math.cos(2 * math.pi / period)
      // g_filt = alpha*src + (1-alpha)*g_filt[1] ... (simplified concept)
      
      float regime_filter = calc_gaussian_filter(close, 50) // Example
      float filter_slope = regime_filter - regime_filter[1]
      float slope_threshold = ta.atr(100) * 0.05 // Dynamic threshold for "flat"
      
      bool is_trending_regime = math.abs(filter_slope) > slope_threshold
      
      // --- MASTER SWITCH ---
      // The final signal is now gated by the regime
      bool final_long_signal = long_signal and is_trending_regime
      bool final_short_signal = short_signal and is_trending_regime
  2. Evolve to a Multi-Timeframe (MTF) Signal Confirmation Engine: This is a significant architectural upgrade from the HTF bias in Level 2. Instead of just checking a trend, we require the KNN model itself to generate a congruent signal on multiple timeframes simultaneously. This confirms the signal has structural depth.

    • Logic: Encapsulate the entire KNN feature calculation and probability scoring logic into a reusable function. Call this function for the chart’s current timeframe. Then, use request.security() to call the same function on a higher timeframe (e.g., 4x the chart’s timeframe). A trade is only triggered if both the local timeframe and the higher timeframe produce a high-probability signal in the same direction.

    • Pine Script Implementation (Conceptual):

      // --- REUSABLE KNN FUNCTION ---
      f_getKnnSignal(k, window, threshold) =>
          // ... all feature calculation and KNN logic from the original script ...
          // Returns a tuple: [prob_up, prob_down]
          [prob_up, prob_down]
      
      // --- MTF CONFIRMATION ---
      [local_prob_up, local_prob_down] = f_getKnnSignal(k_neighbors, window_size, prob_threshold)
      [htf_prob_up, htf_prob_down] = request.security(syminfo.tickerid, htf_timeframe, f_getKnnSignal(k_neighbors, window_size, prob_threshold))
      
      // A much more robust signal
      bool mtf_confirmed_long = ta.crossover(local_prob_up, prob_threshold) and htf_prob_up > prob_threshold
      bool mtf_confirmed_short = ta.crossover(local_prob_down, prob_threshold) and htf_prob_down > prob_threshold

Quantitative Benefit