Sample Freqtrade strategy with indicator sources as hyperoptable CategoricalParameters where possible. Indicators that require OHLCV (ADX, SAR, MFI) are always called with the full dataframe and are NOT hyperoptable for source.
Timeframe
5m
Direction
Long Only
Stoploss
-10.0%
Trailing Stop
No
ROI
0m: 4.0%, 30m: 2.0%, 60m: 1.0%
Interface Version
3
Startup Candles
200
Indicators
8
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# 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 pandas import DataFrame
from freqtrade.strategy import (
IStrategy,
BooleanParameter,
CategoricalParameter,
IntParameter,
)
import talib.abstract as ta
from technical import qtpylib
class SampleCategoricalStrategy(IStrategy):
"""
Sample Freqtrade strategy with indicator sources as hyperoptable CategoricalParameters
where possible. Indicators that require OHLCV (ADX, SAR, MFI) are always called with the full
dataframe and are NOT hyperoptable for source.
"""
INTERFACE_VERSION = 3
can_short = False
minimal_roi = {
"60": 0.01,
"30": 0.02,
"0": 0.04,
}
stoploss = -0.10
trailing_stop = False
timeframe = "5m"
process_only_new_candles = True
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
startup_candle_count = 200
# --- Hyperoptable Indicator Source Parameters (only for 1D price indicators) ---
rsi_src = CategoricalParameter(['close', 'open', 'high', 'low'], default='close', space="buy", optimize=True, load=True)
macd_src = CategoricalParameter(['close', 'open', 'high', 'low'], default='close', space="buy", optimize=True, load=True)
tema_src = CategoricalParameter(['close', 'open', 'high', 'low'], default='close', space="buy", optimize=True, load=True)
boll_src = CategoricalParameter(
['close', 'open', 'high', 'low', 'typical'],
default='typical', space="buy", optimize=True, load=True
)
ewo_src = CategoricalParameter(['close', 'open', 'high', 'low'], default='close', space="buy", optimize=True, load=True)
# --- Other Hyperoptable Parameters ---
buy_rsi = IntParameter(1, 50, default=30, space="buy", optimize=True, load=True)
sell_rsi = IntParameter(50, 100, default=70, space="sell", optimize=True, load=True)
short_rsi = IntParameter(51, 100, default=70, space="sell", optimize=True, load=True)
exit_short_rsi = IntParameter(1, 50, default=30, space="buy", optimize=True, load=True)
ewo_sma1 = IntParameter(2, 20, default=5, space="buy", optimize=True, load=True)
ewo_sma2 = IntParameter(15, 60, default=35, space="buy", optimize=True, load=True)
ewo_use_percent = BooleanParameter(default=True, space="buy", optimize=True, load=True)
order_types = {
"entry": "limit",
"exit": "limit",
"stoploss": "market",
"stoploss_on_exchange": False,
}
order_time_in_force = {"entry": "GTC", "exit": "GTC"}
plot_config = {
"main_plot": {
"tema": {},
"sar": {"color": "white"},
"ewo": {"color": "green"},
},
"subplots": {
"MACD": {
"macd": {"color": "blue"},
"macdsignal": {"color": "orange"},
},
"RSI": {
"rsi": {"color": "red"},
},
},
}
def informative_pairs(self):
return []
def get_src(self, dataframe: DataFrame, src: str):
if src == 'typical':
return qtpylib.typical_price(dataframe)
return dataframe[src]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# --- INDICATORS WITH HYPEROPTABLE SOURCE ---
rsi_src = self.get_src(dataframe, self.rsi_src.value)
macd_src = self.get_src(dataframe, self.macd_src.value)
tema_src = self.get_src(dataframe, self.tema_src.value)
boll_src = self.get_src(dataframe, self.boll_src.value)
ewo_src = self.get_src(dataframe, self.ewo_src.value)
dataframe["rsi"] = ta.RSI(rsi_src)
dataframe["tema"] = ta.TEMA(tema_src, timeperiod=9)
# EWO
sma1 = ta.SMA(ewo_src, timeperiod=self.ewo_sma1.value)
sma2 = ta.SMA(ewo_src, timeperiod=self.ewo_sma2.value)
if self.ewo_use_percent.value:
dataframe["ewo"] = (sma1 - sma2) / ewo_src * 100
else:
dataframe["ewo"] = sma1 - sma2
# --- MACD handling (robust to output types) ---
macd = ta.MACD(macd_src)
# MACD can return a DataFrame (preferred) or a tuple/list/array
if isinstance(macd, pd.DataFrame):
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
elif isinstance(macd, (tuple, list, np.ndarray)):
# MACD returns a tuple/list/array (macd, macdsignal, macdhist)
dataframe["macd"] = macd[0]
dataframe["macdsignal"] = macd[1]
dataframe["macdhist"] = macd[2]
else:
# Fallback for dict
dataframe["macd"] = macd.get("macd", np.nan)
dataframe["macdsignal"] = macd.get("macdsignal", np.nan)
dataframe["macdhist"] = macd.get("macdhist", np.nan)
# --- INDICATORS THAT REQUIRE OHLCV (DO NOT HYPEROPT SOURCE) ---
dataframe["adx"] = ta.ADX(dataframe)
dataframe["sar"] = ta.SAR(dataframe)
dataframe["mfi"] = ta.MFI(dataframe)
# Bollinger Bands (window=20, stds=2) - source is hyperoptable!
bollinger = qtpylib.bollinger_bands(boll_src, 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"]
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Entry: EWO crosses above 0
dataframe.loc[
(
qtpylib.crossed_above(dataframe["ewo"], 0)
& (dataframe["volume"] > 0)
),
"enter_long",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Exit: EWO crosses below 0
dataframe.loc[
(
qtpylib.crossed_below(dataframe["ewo"], 0)
& (dataframe["volume"] > 0)
),
"exit_long",
] = 1
return dataframe