Timeframe
1h
Direction
Long Only
Stoploss
-2.0%
Trailing Stop
No
ROI
0m: 10.0%
Interface Version
3
Startup Candles
N/A
Indicators
1
freqtrade/freqtrade-strategies
freqtrade/freqtrade-strategies
this is an example class, implementing a PSAR based trailing stop loss you are supposed to take the `custom_stoploss()` and `populate_indicators()` parts and adapt it to your own strategy
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
from typing import Optional
import pandas as pd
from pandas import DataFrame
from freqtrade.persistence import Trade
from freqtrade.strategy import (
DecimalParameter,
IStrategy,
IntParameter,
informative,
stoploss_from_absolute,
)
from core.indicators.structure import populate_structure_indicators
class DoubleShunStrategy(IStrategy):
INTERFACE_VERSION = 3
allowed_pairs = {"BTC/USDT:USDT", "BNB/USDT:USDT", "SOL/USDT:USDT"}
can_short: bool = True
timeframe = "1h"
process_only_new_candles = True
startup_candle_count: int = 240
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = True
use_custom_stoploss = True
minimal_roi = {
"0": 0.10,
}
stoploss = -0.02
trend_ema_fast = IntParameter(5, 20, default=10, space="buy", optimize=True)
trend_ema_slow = IntParameter(20, 80, default=30, space="buy", optimize=True)
center_window = IntParameter(4, 12, default=6, space="buy", optimize=True)
pullback_window = IntParameter(3, 8, default=4, space="buy", optimize=True)
restart_window = IntParameter(2, 6, default=3, space="buy", optimize=True)
triangle_window = IntParameter(4, 12, default=6, space="buy", optimize=True)
compression_window = IntParameter(4, 12, default=6, space="buy", optimize=True)
swing_window = IntParameter(2, 8, default=3, space="sell", optimize=True)
pullback_depth = DecimalParameter(0.002, 0.025, default=0.010, decimals=3, space="buy", optimize=True)
breakout_buffer = DecimalParameter(0.001, 0.012, default=0.002, decimals=3, space="buy", optimize=True)
compression_limit = DecimalParameter(0.006, 0.040, default=0.018, decimals=3, space="buy", optimize=True)
level_tolerance = DecimalParameter(0.002, 0.020, default=0.006, decimals=3, space="buy", optimize=True)
level_proximity = DecimalParameter(0.002, 0.020, default=0.006, decimals=3, space="buy", optimize=True)
volume_multiplier = DecimalParameter(1.00, 2.20, default=1.10, decimals=2, space="buy", optimize=True)
@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 2,
}
]
@staticmethod
def _bool_series(dataframe: DataFrame, column: str) -> pd.Series:
return dataframe[column].eq(True)
@informative("1d")
def populate_indicators_1d(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return self._populate_structure_indicators(dataframe)
def _populate_structure_indicators(self, dataframe: DataFrame) -> DataFrame:
return populate_structure_indicators(
dataframe=dataframe,
trend_ema_fast=int(self.trend_ema_fast.value),
trend_ema_slow=int(self.trend_ema_slow.value),
center_window=int(self.center_window.value),
pullback_window=int(self.pullback_window.value),
restart_window=int(self.restart_window.value),
triangle_window=int(self.triangle_window.value),
compression_window=int(self.compression_window.value),
swing_window=int(self.swing_window.value),
pullback_depth=float(self.pullback_depth.value),
breakout_buffer=float(self.breakout_buffer.value),
compression_limit=float(self.compression_limit.value),
level_tolerance=float(self.level_tolerance.value),
level_proximity=float(self.level_proximity.value),
volume_multiplier=float(self.volume_multiplier.value),
)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return self._populate_structure_indicators(dataframe)
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
if metadata["pair"] not in self.allowed_pairs:
return dataframe
hourly_long_context = (
self._bool_series(dataframe, "restart_ready_long_1d")
& self._bool_series(dataframe, "daily_momentum_long_1d")
)
hourly_short_context = self._bool_series(dataframe, "restart_ready_short_1d")
triangle_long = self._bool_series(dataframe, "triangle_breakout_long")
compression_long = self._bool_series(dataframe, "compression_breakout_long")
center_short = self._bool_series(dataframe, "center_breakout_short")
compression_short = self._bool_series(dataframe, "compression_breakout_short")
triangle_long_1d = self._bool_series(dataframe, "triangle_breakout_long_1d")
center_long_1d = self._bool_series(dataframe, "center_breakout_long_1d")
triangle_short_1d = self._bool_series(dataframe, "triangle_breakout_short_1d")
center_short_1d = self._bool_series(dataframe, "center_breakout_short_1d")
daily_long_context = self._bool_series(dataframe, "restart_ready_long_1d")
daily_short_context = self._bool_series(dataframe, "restart_ready_short_1d")
daily_long_signal = daily_long_context & (triangle_long_1d | center_long_1d)
daily_short_signal = daily_short_context & (triangle_short_1d | center_short_1d)
daily_long_trigger = daily_long_signal & ~daily_long_signal.shift(1).eq(True)
daily_short_trigger = daily_short_signal & ~daily_short_signal.shift(1).eq(True)
strong_hourly_long_triangle = (
hourly_long_context
& triangle_long
& self._bool_series(dataframe, "range_tight")
& self._bool_series(dataframe, "ema_slow_slope_up")
& (dataframe["rsi"] > 52)
& self._bool_series(dataframe, "breakout_above_recent_1d")
)
dataframe.loc[
strong_hourly_long_triangle,
["enter_long", "enter_tag"],
] = (1, "long_1h_triangle")
dataframe.loc[
daily_long_trigger & triangle_long_1d,
["enter_long", "enter_tag"],
] = (1, "long_1d_triangle")
dataframe.loc[
daily_long_trigger & center_long_1d,
["enter_long", "enter_tag"],
] = (1, "long_1d_center_compression")
dataframe.loc[
hourly_short_context & center_short,
["enter_short", "enter_tag"],
] = (1, "short_1h_center")
dataframe.loc[
hourly_short_context & compression_short,
["enter_short", "enter_tag"],
] = (1, "short_1h_compression")
dataframe.loc[
daily_short_trigger & triangle_short_1d,
["enter_short", "enter_tag"],
] = (1, "short_1d_triangle")
dataframe.loc[
daily_short_trigger & center_short_1d,
["enter_short", "enter_tag"],
] = (1, "short_1d_center_compression")
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return dataframe
def custom_stoploss(
self,
pair: str,
trade: Trade,
current_time,
current_rate: float,
current_profit: float,
after_fill: bool,
**kwargs,
) -> Optional[float]:
if not self.dp:
return self.stoploss
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty:
return self.stoploss
candle = dataframe.iloc[-1]
tag = trade.enter_tag or ""
scope = "1d" if "_1d_" in tag else "1h"
suffix = "_1d" if scope == "1d" else ""
if trade.is_short:
structure_stop = candle.get(f"structure_stop_short{suffix}")
capped_stop = trade.open_rate * 1.02
stop_price = capped_stop
if pd.notna(structure_stop):
stop_price = min(float(structure_stop), capped_stop)
else:
structure_stop = candle.get(f"structure_stop_long{suffix}")
capped_stop = trade.open_rate * 0.98
stop_price = capped_stop
if pd.notna(structure_stop):
stop_price = max(float(structure_stop), capped_stop)
return stoploss_from_absolute(
stop_price,
current_rate,
is_short=trade.is_short,
leverage=trade.leverage,
)
def custom_exit(
self,
pair: str,
trade: Trade,
current_time,
current_rate: float,
current_profit: float,
**kwargs,
) -> Optional[str]:
if not self.dp:
return None
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty:
return None
candle = dataframe.iloc[-1]
tag = trade.enter_tag or ""
scope = "1d" if "_1d_" in tag else "1h"
suffix = "_1d" if scope == "1d" else ""
stop_long = candle.get(f"structure_stop_long{suffix}")
stop_short = candle.get(f"structure_stop_short{suffix}")
if trade.is_short:
if bool(candle.get("uptrend_1d", False)):
return "trend_flip_short"
if scope == "1d":
if bool(candle.get("center_up_1d", False)) and candle["close"] > candle.get("ema_fast_1d", candle["close"]):
return "structure_exit_short_1d"
else:
if bool(candle.get("center_up", False)) and candle["close"] > candle.get("ema_fast", candle["close"]):
return "structure_exit_short_1h"
if pd.notna(stop_short) and candle["close"] > stop_short:
return f"swing_exit_short_{scope}"
return None
if bool(candle.get("downtrend_1d", False)):
return "trend_flip_long"
if scope == "1d":
if bool(candle.get("center_down_1d", False)) and candle["close"] < candle.get("ema_fast_1d", candle["close"]):
return "structure_exit_long_1d"
else:
if bool(candle.get("center_down", False)) and candle["close"] < candle.get("ema_fast", candle["close"]):
return "structure_exit_long_1h"
if pd.notna(stop_long) and candle["close"] < stop_long:
return f"swing_exit_long_{scope}"
return None