Skip to content
All library documents

Combining Stochastic Reversals with a Signal-to-Noise Filter

Article Strategy library · Author: QuantConnect

Summary

This strategy combines a stochastic-based reversal signal with a signal-to-noise measure and trades only when both components agree. The description uses fast and slow stochastic values and recent price changes to identify a reversal, with a threshold determining its direction. Separately, it calculates a signal-to-noise series over a stated lookback and compares it with a simple moving average. Agreement produces a long or short position; otherwise the strategy closes its position. An optional setting reverses the direction of agreed signals. The document gives parameter defaults and a short Bitcoin futures test interval, but reports no returns, risk statistics, or benchmark comparison.

The proposed benefit is that confirmation may filter some isolated signals, though it can also delay entries or skip trades. The text acknowledges parameter sensitivity, lag, complexity, and the need for risk controls such as stop losses. There is a material discrepancy between the prose and source: the prose describes buying when fast stochastic is below its threshold and a particular signal-to-noise crossover, while the code applies different stochastic comparisons and assigns its signal-to-noise directions through a direct comparison with the moving average. The actual rules should therefore be verified before evaluating the strategy.

Key ideas

  • The strategy requires agreement between a stochastic reversal component and a signal-to-noise component.
  • The stochastic component uses recent price changes and fast and slow stochastic values to produce direction signals.
  • The signal-to-noise component compares its calculated series with a simple moving average.
  • When the two component signals do not agree, the code closes the position.
  • The prose and code describe different signal conditions, and the document reports no performance results.

Tags

Full text
# RsiAlphaModel


# RsiAlphaModel









Uses Wilder's RSI to create insights.
    Using default settings, a cross over below 30 or above 70 will trigger a new insight.

## Source (Apache-2.0)

```python
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *
from QuantConnect.Logging import *
from enum import Enum

class RsiAlphaModel(AlphaModel):
    '''Uses Wilder's RSI to create insights.
    Using default settings, a cross over below 30 or above 70 will trigger a new insight.'''

    def __init__(self,
                 period = 14,
                 resolution = Resolution.DAILY):
        '''Initializes a new instance of the RsiAlphaModel class
        Args:
            period: The RSI indicator period'''
        self.period = period
        self.resolution = resolution
        self.insight_period = Time.multiply(Extensions.to_time_span(resolution), period)
        self.symbol_data_by_symbol ={}

        self.name = '{}({},{})'.format(self.__class__.__name__, period, resolution)

    def update(self, algorithm, data):
        '''Updates this alpha model with the latest data from the algorithm.
        This is called each time the algorithm receives data for subscribed securities
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        insights = []
        for symbol, symbol_data in self.symbol_data_by_symbol.items():
            rsi = symbol_data.rsi
            previous_state = symbol_data.state
            state = self.get_state(rsi, previous_state)

            if state != previous_state and rsi.is_ready:
                if state == State.TRIPPED_LOW:
                    insights.append(Insight.price(symbol, self.insight_period, InsightDirection.UP))
                if state == State.TRIPPED_HIGH:
                    insights.append(Insight.price(symbol, self.insight_period, InsightDirection.DOWN))

            symbol_data.state = state

        return insights


    def on_securities_changed(self, algorithm, changes):
        '''Cleans out old security data and initializes the RSI for any newly added securities.
        Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''
        # clean up data for removed securities
        for security in changes.removed_securities:
            symbol_data = self.symbol_data_by_symbol.pop(security.symbol, None)
            if symbol_data:
                symbol_data.dispose()

        # initialize data for added securities
        added_symbols = []
        for security in changes.added_securities:
            symbol = security.symbol
            if symbol not in self.symbol_data_by_symbol:
                symbol_data = SymbolData(algorithm, symbol, self.period, self.resolution)
                self.symbol_data_by_symbol[symbol] = symbol_data
                added_symbols.append(symbol)

        if added_symbols:
            history = algorithm.history[TradeBar](added_symbols, self.period, self.resolution)
            for trade_bars in history:
                for bar in trade_bars.values():
                    self.symbol_data_by_symbol[bar.symbol].update(bar)


    def get_state(self, rsi, previous):
        ''' Determines the new state. This is basically cross-over detection logic that
        includes considerations for bouncing using the configured bounce tolerance.'''
        if rsi.current.value > 70:
            return State.TRIPPED_HIGH
        if rsi.current.value < 30:
            return State.TRIPPED_LOW
        if previous == State.TRIPPED_LOW:
            if rsi.current.value > 35:
                return State.MIDDLE
        if previous == State.TRIPPED_HIGH:
            if rsi.current.value < 65:
                return State.MIDDLE

        return previous


class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    def __init__(self, algorithm, symbol, period, resolution):
        self.algorithm = algorithm
        self.symbol = symbol
        self.state = State.MIDDLE

        self.rsi = RelativeStrengthIndex(period, MovingAverageType.WILDERS)
        self.consolidator = algorithm.resolve_consolidator(symbol, resolution)
        algorithm.register_indicator(symbol, self.rsi, self.consolidator)

    def update(self, bar):
        self.consolidator.update(bar)

    def dispose(self):
        self.algorithm.subscription_manager.remove_consolidator(self.symbol, self.consolidator)


class State(Enum):
    '''Defines the state. This is used to prevent signal spamming and aid in bounce detection.'''
    TRIPPED_LOW = 0
    MIDDLE = 1
    TRIPPED_HIGH = 2

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.