Short-only scalp strategy. Activates exclusively when HMM detects BEAR regime. Entry: RSI > 70 + close > BB_upper + BEAR regime confirmed. Exit: RSI < 50 OR close < BB_middle (2-candle persistence). Complements ScalpV1 (long) to exploit both sides of the market.
Timeframe
5m
Direction
Long & Short
Stoploss
-2.0%
Trailing Stop
Yes
ROI
0m: 2.0%, 30m: 1.5%, 60m: 1.0%, 120m: 0.5%
Interface Version
3
Startup Candles
N/A
Indicators
1
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
import json
import logging
import sys
from datetime import datetime
from pathlib import Path
from pandas import DataFrame
# Resolve project root from this file's location (works on any machine)
_BASE = Path(__file__).resolve().parent.parent.parent
if str(_BASE) not in sys.path:
sys.path.insert(0, str(_BASE))
from freqtrade.strategy import IStrategy
from qnt.oracle.hmm_regime import detect_regime
from qnt.thesis.thesis_reader import read_thesis
from risk.risk_manager import run_all_checks
from risk.stake_sizer import get_stake_multiplier
from sentiment.reader import get_current_sentiment
logger = logging.getLogger(__name__)
class BearScalpV1(IStrategy):
"""
Short-only scalp strategy. Activates exclusively when HMM detects BEAR regime.
Entry: RSI > 70 + close > BB_upper + BEAR regime confirmed.
Exit: RSI < 50 OR close < BB_middle (2-candle persistence).
Complements ScalpV1 (long) to exploit both sides of the market.
"""
INTERFACE_VERSION = 3
can_short = True
timeframe = "5m"
stoploss = -0.02
trailing_stop = True
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.015
minimal_roi = {
"0": 0.02,
"30": 0.015,
"60": 0.01,
"120": 0.005,
}
startup_candle_count: int = 30
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
from qnt.polars_indicators import add_bollinger_bands, add_rsi, add_sma
from qnt.polars_ohlcv import ohlcv_to_pandas, pandas_to_polars
df_pl = pandas_to_polars(dataframe)
df_pl = add_rsi(df_pl, period=14, alias="rsi")
df_pl = add_bollinger_bands(df_pl, period=20, std_dev=2.0, prefix="bb")
df_pl = df_pl.rename({"bb_mid": "bb_middle"})
df_pl = add_sma(df_pl, period=20, column="volume", alias="volume_avg")
dataframe = ohlcv_to_pandas(df_pl)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
sentiment = get_current_sentiment()
regime = detect_regime(dataframe, metadata["pair"])
# Only short in confirmed BEAR regime
bear_confirmed = regime == "BEAR"
dataframe.loc[
(
bear_confirmed
& (dataframe["rsi"] > 70)
& (dataframe["close"] > dataframe["bb_upper"])
& (dataframe["volume"] > dataframe["volume_avg"])
& (sentiment["score"] <= 0.3) # not strongly bullish
),
"enter_short",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
raw_exit = (dataframe["rsi"] < 50) | (dataframe["close"] < dataframe["bb_middle"])
# 2-candle persistence — same whipsaw protection as long strategies
dataframe.loc[raw_exit & raw_exit.shift(1).fillna(False), "exit_short"] = 1
return dataframe
def custom_stake_amount(
self,
current_time,
current_rate,
proposed_stake,
min_stake,
max_stake,
leverage,
entry_tag,
side,
**kwargs,
):
multiplier = get_stake_multiplier("BearScalpV1")
stake = proposed_stake * multiplier
if min_stake is not None:
stake = max(stake, min_stake)
return min(stake, max_stake)
def confirm_trade_entry(
self,
pair: str,
order_type: str,
amount: float,
rate: float,
time_in_force: str,
current_time: datetime,
entry_tag: str,
side: str,
**kwargs,
) -> bool:
# Block longs — this strategy is short-only
if side != "short":
return False
thesis = read_thesis(pair)
if thesis.get("bias") == "BUY":
logger.info(f"[BEAR_SCALP BLOCK] {pair} thesis=BUY — not shorting into bullish thesis")
return False
try:
total_balance = self.wallets.get_total("USDT")
state_file = Path.home() / "cipher" / "risk" / "balance_state.json"
state = json.loads(state_file.read_text()) if state_file.exists() else {}
risk_result = run_all_checks(
current_balance=total_balance,
start_of_day_balance=state.get("start_of_day", total_balance),
start_of_week_balance=state.get("start_of_week", total_balance),
trade_amount_usdt=amount * rate,
trades_last_hour=0,
recent_trades=[],
min_sentiment="NEUTRAL",
)
if not risk_result["safe_to_trade"]:
return False
except Exception as e:
logger.warning(f"[BEAR_SCALP] Risk check error: {e}")
return True