Timeframe
5m
Direction
Long & Short
Stoploss
-0.3%
Trailing Stop
No
ROI
0m: 0.5%
Interface Version
3
Startup Candles
50
Indicators
2
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
OsirisShort — Short-Only Mean-Reversion on Overbought
======================================================
Only enters the FIRST short of each overbought episode.
SL/TP both intra-candle via native stoploss + minimal_roi.
"""
import logging
from pandas import DataFrame
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, IntParameter
import talib.abstract as ta
logger = logging.getLogger(__name__)
class OsirisShort(IStrategy):
INTERFACE_VERSION = 3
can_short = True
timeframe = "5m"
stoploss = -0.003 # 0.3% SL (intra-candle)
minimal_roi = {"0": 0.005} # 0.5% TP (intra-candle)
trailing_stop = False
use_custom_stoploss = False
startup_candle_count = 50
process_only_new_candles = True
rsi_ob = IntParameter(60, 78, default=65, space="buy", optimize=True)
max_hold = IntParameter(6, 24, default=10, space="sell", optimize=True)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
dataframe["bb_upper"] = bb["upperband"]
dataframe["bb_mid"] = bb["middleband"]
dataframe["bb_lower"] = bb["lowerband"]
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
ob = int(self.rsi_ob.value)
has_data = dataframe["rsi"].notna() & (dataframe["volume"] > 0)
# Core signal: RSI overbought + at BB upper
raw_signal = (
has_data
& (dataframe["rsi"] > ob)
& (dataframe["close"] >= dataframe["bb_upper"])
)
# FRESH ENTRY FILTER: require RSI was BELOW threshold recently
# This ensures we only catch the FIRST entry of each overbought episode
# not repeated entries while RSI stays high (trend continuation)
was_below = (dataframe["rsi"].shift(1) <= ob) | (dataframe["rsi"].shift(2) <= ob) | (dataframe["rsi"].shift(3) <= ob)
fresh_signal = raw_signal & was_below
dataframe.loc[fresh_signal, "enter_short"] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return dataframe
def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs):
if trade.open_date_utc:
minutes = (current_time - trade.open_date_utc).total_seconds() / 60
candles = minutes / 5
if candles >= self.max_hold.value:
return "timeout"
return None
def leverage(self, pair, current_time, current_rate, proposed_leverage,
max_leverage, entry_tag, side, **kwargs) -> float:
return 1.0