Базовая рабочая стратегия: - Лонги по тренду (EMA50 > EMA200) + импульс (RSI > 55) - Фильтр волатильности (ATR% > 0.25) - Выход: ослабление импульса (RSI < 45) или слом тренда (EMA50 < EMA200) Примечание: шорты отключены на первом этапе.
Timeframe
5m
Direction
Long Only
Stoploss
N/A
Trailing Stop
No
ROI
0m: 0.6%, 30m: 0.3%, 90m: 0.0%
Interface Version
N/A
Startup Candles
200
Indicators
3
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import pandas as pd
import numpy as np
class NewsHeliusBitqueryML(IStrategy):
"""
Базовая рабочая стратегия:
- Лонги по тренду (EMA50 > EMA200) + импульс (RSI > 55)
- Фильтр волатильности (ATR% > 0.25)
- Выход: ослабление импульса (RSI < 45) или слом тренда (EMA50 < EMA200)
Примечание: шорты отключены на первом этапе.
"""
timeframe = "5m"
can_short = False
process_only_new_candles = False
use_exit_signal = True
startup_candle_count = 200 # нужно для EMA200/ATR
# --- индикаторы ---
@staticmethod
def _ema(series: pd.Series, period: int) -> pd.Series:
return series.ewm(span=period, adjust=False).mean()
@staticmethod
def _rsi(series: pd.Series, period: int = 14) -> pd.Series:
delta = series.diff()
up = np.where(delta > 0, delta, 0.0)
down = np.where(delta < 0, -delta, 0.0)
roll_up = pd.Series(up, index=series.index).ewm(alpha=1/period, adjust=False).mean()
roll_down = pd.Series(down, index=series.index).ewm(alpha=1/period, adjust=False).mean()
rs = roll_up / (roll_down.replace(0, np.nan))
rsi = 100 - (100 / (1 + rs))
return rsi.fillna(50) # нейтраль для первых баров
@staticmethod
def _atr(df: DataFrame, period: int = 14) -> pd.Series:
high = df["high"]
low = df["low"]
close = df["close"]
prev_close = close.shift(1)
tr = pd.concat([
(high - low),
(high - prev_close).abs(),
(low - prev_close).abs()
], axis=1).max(axis=1)
atr = tr.ewm(alpha=1/period, adjust=False).mean()
return atr
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
# EMA тренд
df["ema_fast"] = self._ema(df["close"], 50)
df["ema_slow"] = self._ema(df["close"], 200)
# RSI импульс
df["rsi"] = self._rsi(df["close"], 14)
# ATR волатильность и относительная волатильность
df["atr"] = self._atr(df, 14)
df["atr_pct"] = (df["atr"] / df["close"]) * 100
# Фильтр объёма (опционально): текущий > средний за 20
df["vol_ma20"] = df["volume"].rolling(20).mean()
df["vol_ok"] = (df["volume"] > df["vol_ma20"]).astype(int)
# Чистим начальные NaN
df.fillna(method="ffill", inplace=True)
df.dropna(inplace=True)
return df
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
trend_up = df["ema_fast"] > df["ema_slow"]
momentum_ok = df["rsi"] > 55
vola_ok = df["atr_pct"] > 0.25
vol_ok = df["vol_ok"] == 1
# Базовый лонг-сигнал
df["enter_long"] = (trend_up & momentum_ok & vola_ok & vol_ok).astype(int)
# Шорты отключены
df["enter_short"] = 0
return df
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
exit_momentum_weak = df["rsi"] < 45
exit_trend_break = df["ema_fast"] < df["ema_slow"]
df["exit_long"] = (exit_momentum_weak | exit_trend_break).astype(int)
df["exit_short"] = 0
return df