Timeframe
5m
Direction
Long & Short
Stoploss
-0.4%
Trailing Stop
Yes
ROI
0m: 0.6%, 15m: 0.4%, 45m: 0.2%, 90m: 0.0%
Interface Version
N/A
Startup Candles
320
Indicators
2
freqtrade/freqtrade-strategies
author@: lenik
import logging
from typing import Dict
import numpy as np
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
logger = logging.getLogger(__name__)
class IntradayScalp5m(IStrategy):
timeframe = "5m"
can_short = True
process_only_new_candles = True
# Должно покрывать объёмный квантиль и Keltner
startup_candle_count = 320
# Короткая лесенка ROI (будет переоптимизировано hyperopt'ом)
minimal_roi = {"0": 0.006, "15": 0.004, "45": 0.002, "90": 0.0}
# Типы ордеров
order_types = {
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": True,
"stoploss_on_exchange_interval": 60,
"stoploss_on_exchange_limit_ratio": 0.99,
}
order_time_in_force = {"entry": "gtc", "exit": "gtc"}
# Узкий SL и трейлинг (будут переоптимизированы)
stoploss = -0.004
trailing_stop = True
trailing_stop_positive = 0.003
trailing_stop_positive_offset = 0.006
trailing_only_offset_is_reached = True
# -----------------
# ПАРАМЕТРЫ (для гиперопта/тонкой настройки)
# -----------------
# Время торговли 07:00–20:00 UTC фиксировано фильтром времени
# Keltner: EMA20 +/- atr_mult * ATR(14)
atr_mult = DecimalParameter(1.0, 2.0, default=1.2, decimals=2, space="buy", optimize=True)
# Волатильность: ATR% в диапазоне
atr_min_pct = DecimalParameter(0.001, 0.010, default=0.0020, decimals=4, space="buy", optimize=True)
atr_max_pct = DecimalParameter(0.005, 0.020, default=0.0120, decimals=4, space="buy", optimize=True)
# Объём: значение >= rolling-quantile(volume, q)
vol_q_window = IntParameter(96, 300, default=144, space="buy", optimize=True)
vol_q_thres = DecimalParameter(0.50, 0.85, default=0.60, decimals=2, space="buy", optimize=True)
# Protection params (для hyperopt пространства "protection")
mdd_stop_candles = IntParameter(200, 400, default=288, space="protection", optimize=True)
mdd_max_dd = DecimalParameter(0.010, 0.020, default=0.015, decimals=3, space="protection", optimize=True)
lpp_lb_candles = IntParameter(5, 10, default=8, space="protection", optimize=True)
lpp_stop_candles = IntParameter(5, 12, default=8, space="protection", optimize=True)
lpp_required_profit = DecimalParameter(-0.001, 0.002, default=0.0, decimals=3, space="protection", optimize=True)
def informative_pairs(self):
return []
@property
def protections(self):
# 5m: ~288 свечей = 1 день
return [
# Дневной стоп по счёту 1.5%, кулдаун ~до конца дня
{
"method": "MaxDrawdown",
"lookback_period_candles": 288,
"trade_limit": 1,
"stop_duration_candles": int(self.mdd_stop_candles.value),
"max_allowed_drawdown": float(self.mdd_max_dd.value),
},
# Кулдаун после убыточной сделки (на пару)
{
"method": "LowProfitPairs",
"lookback_period_candles": int(self.lpp_lb_candles.value),
"trade_limit": 1,
"stop_duration_candles": int(self.lpp_stop_candles.value),
"required_profit": float(self.lpp_required_profit.value),
},
]
# -----------------
# Индикаторы
# -----------------
def populate_indicators(self, df: DataFrame, metadata: Dict) -> DataFrame:
# EMA20 (центральная линия Keltner)
df["ema20"] = df["close"].ewm(span=20, adjust=False).mean()
# ATR(14)
prev_close = df["close"].shift(1)
tr = np.maximum(
df["high"] - df["low"],
np.maximum((df["high"] - prev_close).abs(), (df["low"] - prev_close).abs()),
)
df["atr"] = tr.ewm(alpha=1 / 14, adjust=False).mean()
df["atr_pct"] = df["atr"] / df["close"]
# Keltner Channels
atr_mult_val = float(self.atr_mult.value)
df["kc_upper"] = df["ema20"] + atr_mult_val * df["atr"]
df["kc_lower"] = df["ema20"] - atr_mult_val * df["atr"]
# Объёмный перцентиль (rolling quantile)
win = int(self.vol_q_window.value)
q = float(self.vol_q_thres.value)
# Используем min_periods=win для стабильности порога
df["vol_q"] = df["volume"].rolling(win, min_periods=win).quantile(q)
df["vol_ok"] = (df["volume"] >= df["vol_q"]).astype(int)
# Фильтр времени UTC: 07:00–20:00
hours = df["date"].dt.hour
df["in_session"] = ((hours >= 7) & (hours <= 20)).astype(int)
df.replace([np.inf, -np.inf], np.nan, inplace=True)
df.ffill(inplace=True)
df.bfill(inplace=True)
return df
# -----------------
# Входы
# -----------------
def populate_entry_trend(self, df: DataFrame, metadata: Dict) -> DataFrame:
# Волатильность в заданном диапазоне
vol_ok = (df["atr_pct"] >= float(self.atr_min_pct.value)) & (
df["atr_pct"] <= float(self.atr_max_pct.value)
)
base_filters = vol_ok & (df["vol_ok"] == 1) & (df["in_session"] == 1)
# LONG: выход ниже нижней ленты и возврат внутрь (пересечение снизу-вверх)
long_revert = (df["close"].shift(1) < df["kc_lower"].shift(1)) & (df["close"] > df["kc_lower"])
# SHORT: зеркально по верхней ленте (пересечение сверху-вниз)
short_revert = (df["close"].shift(1) > df["kc_upper"].shift(1)) & (df["close"] < df["kc_upper"])
df["enter_long"] = 0
df.loc[base_filters & long_revert, "enter_long"] = 1
df["enter_short"] = 0
df.loc[base_filters & short_revert, "enter_short"] = 1
return df
# -----------------
# Выходы управляются ROI/SL/Trailing — явных сигналов выхода не задаём
# -----------------
def populate_exit_trend(self, df: DataFrame, metadata: Dict) -> DataFrame:
df["exit_long"] = 0
df["exit_short"] = 0
return df