💬 Forum

EchoL1StrategyV6

🏆 League #1550 / 1939

crow2673/Echo-core/backtests/strategies/EchoL1StrategyV6.py · ★1 · first seen 2026-07-16 · repo updated 2026-05-03 · ⬇ 1 download

Basics mode: futures timeframe: 1h interface version: 3 1d
Settings stoploss: -0.02 has minimal roi
Indicators MACD RSI talib
Concepts mean_reversion
15 related strategies ( identical code, similar name)

Each tile is a different kind of check — from an instant code lint to full sandboxed backtests and forward tests on recent data. Not sure what a check actually proves? See the FAQ →

Source

Download Raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
"""
EchoL1StrategyV6 — Long AND short. Profits in any market direction.

V4/V5 problem: long-only strategy loses in bear/sideways markets.
An AI should profit regardless of direction.

V6 adds short selling:
  Long:  RSI recovery from oversold + MACD turning up   + near MA50 + volume
  Short: RSI reversal from overbought + MACD turning down + near MA50 + volume

Exit: 5% TP | 2% SL (tighter — crypto moves fast both ways)
No trailing stop — let signals run to target.

Expected: long signals fire in dips, short signals fire at tops.
Net result: profits in bull, bear, AND sideways markets.
"""
from freqtrade.strategy import IStrategy, informative
from pandas import DataFrame
import talib.abstract as ta
import pandas as pd


class EchoL1StrategyV6(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "1h"

    minimal_roi = {"0": 0.05}
    stoploss = -0.02
    trailing_stop = False
    can_short = True

    @informative("1d")
    def populate_indicators_1d(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ma50"] = dataframe["close"].rolling(50).mean()
        dataframe["near_or_above_ma50"] = (
            dataframe["close"] > dataframe["ma50"] * 0.92
        ).astype(int)
        dataframe["near_or_below_ma50"] = (
            dataframe["close"] < dataframe["ma50"] * 1.08
        ).astype(int)
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["rsi"] = ta.RSI(dataframe["close"], timeperiod=14)
        dataframe["vol_ma20"] = dataframe["volume"].rolling(20).mean()

        _macd, _signal, _hist = ta.MACD(dataframe["close"], fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd_hist"] = pd.Series(_hist, index=dataframe.index)
        dataframe["macd_hist_prev"] = dataframe["macd_hist"].shift(1)

        dataframe["rsi_prev"] = dataframe["rsi"].shift(1)

        # Long conditions
        dataframe["rsi_was_oversold"] = (dataframe["rsi_prev"] < 40).astype(int)
        dataframe["rsi_recovering"] = (
            (dataframe["rsi_was_oversold"] == 1) &
            (dataframe["rsi"] > dataframe["rsi_prev"])
        ).astype(int)
        dataframe["macd_turning_up"] = (
            dataframe["macd_hist"] > dataframe["macd_hist_prev"]
        ).astype(int)

        # Short conditions (mirror)
        dataframe["rsi_was_overbought"] = (dataframe["rsi_prev"] > 60).astype(int)
        dataframe["rsi_reversing"] = (
            (dataframe["rsi_was_overbought"] == 1) &
            (dataframe["rsi"] < dataframe["rsi_prev"])
        ).astype(int)
        dataframe["macd_turning_down"] = (
            dataframe["macd_hist"] < dataframe["macd_hist_prev"]
        ).astype(int)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Long entry
        dataframe.loc[
            (dataframe["near_or_above_ma50_1d"] == 1) &
            (dataframe["rsi_recovering"] == 1) &
            (dataframe["rsi"] < 45) &
            (dataframe["macd_turning_up"] == 1) &
            (dataframe["volume"] > dataframe["vol_ma20"] * 1.2) &
            (dataframe["volume"] > 0),
            "enter_long",
        ] = 1

        # Short entry (mirror logic)
        dataframe.loc[
            (dataframe["near_or_below_ma50_1d"] == 1) &
            (dataframe["rsi_reversing"] == 1) &
            (dataframe["rsi"] > 55) &
            (dataframe["macd_turning_down"] == 1) &
            (dataframe["volume"] > dataframe["vol_ma20"] * 1.2) &
            (dataframe["volume"] > 0),
            "enter_short",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, "exit_long"] = 0
        dataframe.loc[:, "exit_short"] = 0
        return dataframe