From f8b9700f70a38a093ef45e1877c30c62f18b0be9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Jan 2023 20:23:33 +0100 Subject: [PATCH] Add volatility stratgegy closes #283 --- .../strategies/futures/VolatilityStrategy.py | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 user_data/strategies/futures/VolatilityStrategy.py diff --git a/user_data/strategies/futures/VolatilityStrategy.py b/user_data/strategies/futures/VolatilityStrategy.py new file mode 100644 index 0000000..3e4c798 --- /dev/null +++ b/user_data/strategies/futures/VolatilityStrategy.py @@ -0,0 +1,137 @@ +# flake8: noqa: F401 +# isort: skip_file +# --- Do not remove these libs --- +from datetime import datetime +from typing import Optional + +import numpy as np # noqa +import pandas as pd # noqa +from pandas import DataFrame + +import talib.abstract as ta +from freqtrade.persistence import Trade +from freqtrade.strategy import (CategoricalParameter, DecimalParameter, + IntParameter, IStrategy) +import freqtrade.vendor.qtpylib.indicators as qtpylib + +from technical.util import resample_to_interval, resampled_merge + + +class VolatilitySystem(IStrategy): + """ + Volatility System strategy. + Based on https://www.tradingview.com/script/3hhs0XbR/ + + """ + can_short: bool = True + + minimal_roi = { + "0": 100 + } + + stoploss = -0.10 + + # Optimal ticker interval for the strategy + timeframe = '1h' + + plot_config = { + # Main plot indicators (Moving averages, ...) + 'main_plot': { + }, + 'subplots': { + "Volatility system": { + "atr": {"color": "white"}, + "abs_close_change": {"color": "red"}, + } + } + } + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Adds several indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let easyprofiler do the work for you in finding out which indicators + are worth adding. + """ + resample_int = 60 * 3 + resampled = resample_to_interval(dataframe, resample_int) + # Average True Range (ATR) + resampled['atr'] = ta.ATR(resampled, timeperiod=14) * 2.0 + # Absolute close change + resampled['close_change'] = resampled['close'].diff() + resampled['abs_close_change'] = resampled['close_change'].abs() + + dataframe = resampled_merge(dataframe, resampled, fill_na=True) + dataframe['atr'] = dataframe[f'resample_{resample_int}_atr'] + dataframe['close_change'] = dataframe[f'resample_{resample_int}_close_change'] + dataframe['abs_close_change'] = dataframe[f'resample_{resample_int}_abs_close_change'] + + # Average True Range (ATR) + # dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) * 2.0 + # Absolute close change + # dataframe['close_change'] = dataframe['close'].diff() + # dataframe['abs_close_change'] = dataframe['close_change'].abs() + + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Based on TA indicators, populates the buy and sell signals for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy and sell columns + """ + # Use qtpylib.crossed_above to get only one signal, otherwise the signal is active + # for the whole "long" timeframe. + dataframe.loc[ + # qtpylib.crossed_above(dataframe['close_change'] * 1, dataframe['atr']), + (dataframe['close_change'] * 1 > dataframe['atr'].shift(1)), + 'enter_long'] = 1 + dataframe.loc[ + # qtpylib.crossed_above(dataframe['close_change'] * -1, dataframe['atr']), + (dataframe['close_change'] * -1 > dataframe['atr'].shift(1)), + 'enter_short'] = 1 + + return dataframe + + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + use sell/buy signals as long/short indicators + """ + dataframe.loc[ + dataframe['enter_long'] == 1, + 'exit_short'] = 1 + dataframe.loc[ + dataframe['enter_short'] == 1, + 'exit_long'] = 1 + return dataframe + + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + proposed_stake: float, min_stake: Optional[float], max_stake: float, + leverage: float, entry_tag: Optional[str], side: str, + **kwargs) -> float: + # 50% stake amount on initial entry + return proposed_stake / 2 + + position_adjustment_enable = True + + def adjust_trade_position(self, trade: Trade, current_time: datetime, + current_rate: float, current_profit: float, + min_stake: Optional[float], max_stake: float, + current_entry_rate: float, current_exit_rate: float, + current_entry_profit: float, current_exit_profit: float, + **kwargs) -> Optional[float]: + dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) + # Only buy when not actively falling price. + if len(dataframe) > 2: + last_candle = dataframe.iloc[-1].squeeze() + previous_candle = dataframe.iloc[-2].squeeze() + signal_name = 'enter_long' if not trade.is_short else 'enter_short' + # Only enlarge position on new signal. + if ( + last_candle[signal_name] == 1 + and previous_candle[signal_name] != 1 + and trade.nr_of_successful_entries < 2 + ): + return trade.stake_amount + return None