# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Dict, Optional, Union, Tuple
from freqtrade.strategy import (
IStrategy,
Trade,
Order,
PairLocks,
informative,
# Hyperopt Parameters
BooleanParameter,
CategoricalParameter,
DecimalParameter,
IntParameter,
RealParameter,
# timeframe helpers
timeframe_to_minutes,
timeframe_to_next_date,
timeframe_to_prev_date,
# Strategy helper functions
merge_informative_pair,
stoploss_from_absolute,
stoploss_from_open,
AnnotationType,
)
# --------------------------------
import talib.abstract as ta
from technical import qtpylib
class SimpleStrategy(IStrategy):
"""
RSI crossover with trend filter and proper risk management.
Key changes from original:
- Trend filter (EMA50) — don't buy dips in a crash
- Tight stoploss (-6%) — cut losers FAST instead of -22%
- Trailing stop — locks in gains once profit > 5%
- Partial exits — 25% at +4%, 25% at +8%
The original -22% stoploss was the killer. One loss wiped out ~10 wins.
With -6% stop vs +2-4% ROI, the risk/reward is ~1:0.5 which is still
aggressive but the win rate should compensate.
"""
INTERFACE_VERSION = 3
timeframe = "5m"
can_short: bool = False
# Minimal ROI — disabled, using trailing stop + exit signals
minimal_roi = {}
# Tight stoploss — the most important fix
stoploss = -0.06
# Trailing stop: kicks in after +5%, then trails at 3%
trailing_stop = True
trailing_only_offset_is_reached = True
trailing_stop_positive = 0.03
trailing_stop_positive_offset = 0.05
process_only_new_candles = True
# Use exit signals for additional exits
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
# Enable partial exits
position_adjustment_enable = True
max_entry_position_adjustment = 2
startup_candle_count: int = 100
# --- Hyperopt parameters ---
buy_rsi = IntParameter(15, 45, default=30, space="buy", optimize=True)
sell_rsi = IntParameter(60, 85, default=70, space="sell", optimize=True)
order_types = {
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": False,
}
order_time_in_force = {
"entry": "GTC",
"exit": "GTC",
}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Momentum
dataframe["adx"] = ta.ADX(dataframe)
dataframe["rsi"] = ta.RSI(dataframe)
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe["fastd"] = stoch_fast["fastd"]
dataframe["fastk"] = stoch_fast["fastk"]
# MACD
macd = ta.MACD(dataframe)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
# MFI
dataframe["mfi"] = ta.MFI(dataframe)
# Bollinger Bands
bollinger = qtpylib.bollinger_bands(
qtpylib.typical_price(dataframe), window=20, stds=2
)
dataframe["bb_lowerband"] = bollinger["lower"]
dataframe["bb_middleband"] = bollinger["mid"]
dataframe["bb_upperband"] = bollinger["upper"]
dataframe["bb_percent"] = (
(dataframe["close"] - dataframe["bb_lowerband"])
/ (dataframe["bb_upperband"] - dataframe["bb_lowerband"])
)
dataframe["bb_width"] = (
(dataframe["bb_upperband"] - dataframe["bb_lowerband"])
/ dataframe["bb_middleband"]
)
# Trend filter — key addition
dataframe["ema20"] = ta.EMA(dataframe, timeperiod=20)
dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50)
# Volatility
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
# Other indicators from original
dataframe["sar"] = ta.SAR(dataframe)
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
hilbert = ta.HT_SINE(dataframe)
dataframe["htsine"] = hilbert["sine"]
dataframe["htleadsine"] = hilbert["leadsine"]
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
# RSI crosses above buy threshold
(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value))
# Volume check
& (dataframe["volume"] > 0)
# TREND FILTER — price must be above EMA50 (uptrend)
& (dataframe["close"] > dataframe["ema50"])
# Not at the very top of BB (buy the dip)
& (dataframe["bb_percent"] < 0.7)
),
"enter_long",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value))
& (dataframe["volume"] > 0)
),
"exit_long",
] = 1
return dataframe
def adjust_trade_position(self, trade: Trade, current_profit: float, **kwargs) -> Optional[Union[float, Tuple[float, str]]]:
"""
Multi-tier partial exits:
- Tier 1: Sell 25% at +4% profit
- Tier 2: Sell 25% at +8% profit
- Remaining 50% rides the trailing stop
"""
if trade.is_short:
return None
if current_profit >= 0.04 and trade.nr_of_successful_exits == 0:
return (-0.25, "TP1: 25% exit at 4%+ profit")
if current_profit >= 0.08 and trade.nr_of_successful_exits == 1:
return (-0.25, "TP2: 25% exit at 8%+ profit")
return None