Skip to content

turtle_quant_1.strategies.mean_reversion.slow_stoch_osc_crossover

turtle_quant_1.strategies.mean_reversion.slow_stoch_osc_crossover

Slow Stochastic Oscillator crossover strategy implementation.

SlowStochOscCrossover

SlowStochOscCrossover(
    k_period: int = 14, k_smooth: int = 3, d_period: int = 3
)

Bases: BaseStrategy

A strategy that uses Slow Stochastic Oscillator %K/%D crossovers to generate buy and sell signals.

Can be intuitively interpreted as whether a stock is overbought or oversold. Refer to: https://www.investopedia.com/terms/s/stochasticoscillator.asp

Leading / Lagging: Lagging in construction. Leading in interpretation. Lag period (in candles): (k_period - 1) / 2 + (d_period - 1) / 2 + (k_smooth - 1) / 2 Effect: Delay in recognizing that price has deviated far enough from equilibrium.

Initialize the Slow Stochastic Oscillator strategy.

Parameters:

Name Type Description Default
k_period int

The number of candles to calculate %K.

14
k_smooth int

The smoothing period applied to %K to create Slow %K.

3
d_period int

The smoothing period applied to Slow %K to create Slow %D.

3
Source code in src/turtle_quant_1/strategies/mean_reversion/slow_stoch_osc_crossover.py
def __init__(
    self,
    k_period: int = 14,
    k_smooth: int = 3,
    d_period: int = 3,
):
    """Initialize the Slow Stochastic Oscillator strategy.

    Args:
        k_period: The number of candles to calculate %K.
        k_smooth: The smoothing period applied to %K to create Slow %K.
        d_period: The smoothing period applied to Slow %K to create Slow %D.
    """
    super().__init__()
    self.k_period = k_period
    self.k_smooth = k_smooth
    self.d_period = d_period

    # This depends on the resampling of the data
    if k_period > convert_units(BACKTESTING_MAX_LOOKBACK_DAYS, "1D", "1H"):
        raise ValueError(
            f"This strategy relies on too many lookback candles ({k_period}) "
            f"for meaningful evaluation. Maximum lookback is {BACKTESTING_MAX_LOOKBACK_DAYS} days."
        )

generate_historical_scores

generate_historical_scores(
    data: DataFrame, symbol: str
) -> Series

Generate historical scores based on Slow %K/%D crossovers.

Parameters:

Name Type Description Default
data DataFrame

DataFrame with OHLCV data.

required
symbol str

The symbol being analyzed.

required

Returns:

Type Description
Series

Score array with values between -1.0 and +1.0, indexed by the original data's index.

Source code in src/turtle_quant_1/strategies/mean_reversion/slow_stoch_osc_crossover.py
def generate_historical_scores(self, data: pd.DataFrame, symbol: str) -> pd.Series:
    """Generate historical scores based on Slow %K/%D crossovers.

    Args:
        data: DataFrame with OHLCV data.
        symbol: The symbol being analyzed.

    Returns:
        Score array with values between -1.0 and +1.0, indexed by the original data's index.
    """
    self.validate_data(data)

    # TODO: This is a magic number.
    data_resampled = DataUnitConverter.convert_to_1h_data(symbol, data)

    # Fast %K
    lowest_low = data_resampled["Low"].rolling(self.k_period).min()
    highest_high = data_resampled["High"].rolling(self.k_period).max()
    fast_k = (
        100 * (data_resampled["Close"] - lowest_low) / (highest_high - lowest_low)
    )

    # Slow %K (smoothed Fast %K)
    slow_k = fast_k.rolling(self.k_smooth).mean()

    # Slow %D (smoothed Slow %K)
    slow_d = slow_k.rolling(self.d_period).mean()

    # Crossovers: +1 when Slow %K > Slow %D, -1 when Slow %K < Slow %D
    crossover_signal = (slow_k > slow_d).astype(int) - (slow_k < slow_d).astype(int)
    if np.isnan(crossover_signal.iloc[-1]):
        raise ValueError("Last score should not be NaN")

    crossover_signal = crossover_signal.reindex(data.index)
    crossover_signal = crossover_signal.bfill().ffill()

    return pd.Series(
        data=crossover_signal.fillna(0).clip(-1, 1).values,
        index=data.index,
    )

generate_prediction_score

generate_prediction_score(
    data: DataFrame, symbol: str
) -> float

Generate the latest prediction score.

NOTE: Assume that the data is sorted by datetime.

Parameters:

Name Type Description Default
data DataFrame

The data to use for the strategy.

required
symbol str

The symbol being analyzed.

required

Returns:

Type Description
float

A float between -1.0 and +1.0 representing the most recent crossover signal.

Source code in src/turtle_quant_1/strategies/mean_reversion/slow_stoch_osc_crossover.py
def generate_prediction_score(self, data: pd.DataFrame, symbol: str) -> float:
    """Generate the latest prediction score.

    NOTE: Assume that the data is sorted by datetime.

    Args:
        data: The data to use for the strategy.
        symbol: The symbol being analyzed.

    Returns:
        A float between -1.0 and +1.0 representing the most recent crossover signal.
    """
    return self.generate_historical_scores(
        # TODO: Using * 2.0 here to give buffer zone for any miscalculations.
        # This depends on the resampling of the data
        data.iloc[
            -(round(convert_units(self.k_period, "1H", CANDLE_UNIT) * 2.0)) :
        ],
        symbol,
    ).iloc[-1]