Strategy to open simultaneous long and short positions for hedging. This example attempts to always hold both a long and a short position. Includes Hyperopt spaces.
Timeframe
5m
Direction
Long & Short
Stoploss
N/A
Trailing Stop
No
ROI
N/A
Interface Version
3
Startup Candles
N/A
Indicators
1
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# --- Do not remove these imports ---
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, CategoricalParameter
from pandas import DataFrame
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class SimultaneousHedgingStrategy(IStrategy):
"""
Strategy to open simultaneous long and short positions for hedging.
This example attempts to always hold both a long and a short position.
Includes Hyperopt spaces.
"""
# Strategy interface version
INTERFACE_VERSION = 3
# Enable shorting
can_short = True
# THIS IS CRUCIAL for allowing simultaneous long and short on the SAME PAIR
strategy_supports_edged_trading = True
# --- Hyperopt Spaces ---
# ROI table space
roi_profit_target = DecimalParameter(0.005, 0.05, default=0.02, decimals=3, space="buy", optimize=True)
# Stoploss space - Freqtrade expects this to be negative.
# Naming this 'stoploss' (matching the class attribute) allows Freqtrade to auto-assign its .value.
stoploss = DecimalParameter(-0.05, -0.005, default=-0.01, decimals=3, space="buy", optimize=True)
# Trailing stoploss parameters
# Naming these to match strategy attributes allows Freqtrade to auto-assign their .value.
trailing_stop = CategoricalParameter([True, False], default=True, space="buy", optimize=True)
trailing_stop_positive = DecimalParameter(0.005, 0.05, default=0.01, decimals=3, space="buy", optimize=True)
trailing_stop_positive_offset = DecimalParameter(0.002, 0.03, default=0.005, decimals=3, space="buy", optimize=True)
trailing_only_offset_is_reached = CategoricalParameter([True, False], default=True, space="buy", optimize=True)
# Bollinger Bands parameters
bb_window = IntParameter(10, 50, default=20, space="buy", optimize=True)
bb_stds = DecimalParameter(1.5, 3.5, default=2.0, decimals=1, space="buy", optimize=True)
# --- Class Attributes ---
# minimal_roi will be populated in __init__
# stoploss, trailing_stop etc. are now directly defined as Hyperopt parameters above.
def __init__(self, config: dict):
super().__init__(config)
# Construct the minimal_roi dictionary using the value from the hyperopt parameter
self.minimal_roi = {
"0": self.roi_profit_target.value
}
# Optimal timeframe for the strategy.
timeframe = '5m'
# Run "populate_indicators()" only for new candle.
process_only_new_candles = True
# These values can be overridden in the config.
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False # Set to True if you don't want ROI exits to prevent re-entry
# Number of candles the strategy requires before producing valid signals
# This should be dynamic based on bb_window.value if possible, or set to a max sensible value.
# For simplicity, we'll use a fixed value, but for robust hyperopt, this should be considered.
@property
def startup_candle_count(self) -> int:
return self.bb_window.value # Or max(self.bb_window.value, any_other_lookback_period)
# Optional order type mapping.
order_types = {
'entry': 'limit',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False # Set to True if supported and desired
}
# Optional order time in force.
order_time_in_force = {
'entry': 'gtc',
'exit': 'gtc'
}
plot_config = {
'main_plot': {
'bb_upperband': {'color': 'blue'},
'bb_middleband': {'color': 'orange'},
'bb_lowerband': {'color': 'blue'},
},
'subplots': {} # No subplots needed for this simple BB strategy
}
# def informative_pairs(self):
# return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds Technical Analysis indicators to the given DataFrame
"""
# Bollinger Bands
bollinger = qtpylib.bollinger_bands(
qtpylib.typical_price(dataframe),
window=self.bb_window.value,
stds=self.bb_stds.value
)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Always attempt to have a long and a short position.
Freqtrade will manage not opening a new position if one of that side already exists.
"""
# Set conditions to True to always attempt to enter if a position for that side is not already open.
# Freqtrade handles the logic of not opening duplicate positions (e.g. if a long is open, it won't open another long).
# With strategy_supports_edged_trading=True, it allows one long AND one short simultaneously for the same pair.
# Long entry: Price closes below lower Bollinger Band (Reversal)
dataframe.loc[
(
(dataframe['close'] < dataframe['bb_lowerband']) &
(dataframe['volume'] > 0)
),
['enter_long', 'enter_tag']] = (1, 'bb_lower_reversal_long')
# Short entry: Price closes above upper Bollinger Band (Reversal)
dataframe.loc[
(
(dataframe['close'] > dataframe['bb_upperband']) &
(dataframe['volume'] > 0)
),
['enter_short', 'enter_tag']] = (1, 'bb_upper_reversal_short')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Exit conditions based on Bollinger Bands.
These custom exits will work alongside ROI, stoploss, and trailing stoploss.
"""
# Exit long conditions: Price touches or crosses middle band from below
dataframe.loc[
(
(dataframe['close'] >= dataframe['bb_middleband']) &
(dataframe['volume'] > 0)
),
['exit_long', 'exit_tag']] = (1, 'bb_middle_exit_long')
# Exit short conditions: Price touches or crosses middle band from above
dataframe.loc[
(
(dataframe['close'] <= dataframe['bb_middleband']) &
(dataframe['volume'] > 0)
),
['exit_short', 'exit_tag']] = (1, 'bb_middle_exit_short')
return dataframe