Strategy QFLMulti author@: Sergio Cricca github@: https://github.com/freqtrade/freqtrade-strategies version: 1.0
Timeframe
5m
Direction
Long Only
Stoploss
-90.0%
Trailing Stop
No
ROI
0m: 5.0%, 20m: 4.0%, 30m: 3.0%, 60m: 1.0%
Interface Version
N/A
Startup Candles
N/A
Indicators
0
freqtrade/freqtrade-strategies
author@: lenik
# --- Do not remove these libs ---
from typing import Optional
from freqtrade.strategy import IStrategy
from typing import Dict, List
from functools import reduce
from pandas import DataFrame
import numpy as np
from datetime import datetime, timedelta, timezone
from freqtrade.persistence import Trade
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class QFLMulti(IStrategy):
"""
Strategy QFLMulti
author@: Sergio Cricca
github@: https://github.com/freqtrade/freqtrade-strategies
version: 1.0
"""
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = {
"60": 0.01,
"30": 0.03,
"20": 0.04,
"0": 0.05
}
# # Optimal timeframe for the strategy
# timeframe = '5m'
# trailing stoploss
trailing_stop = False
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.02
stoploss = -0.9
# run "populate_indicators" only for new candle
process_only_new_candles = False
# Experimental settings (configuration will overide these if set)
use_exit_signal = True
exit_profit_only = True
ignore_roi_if_entry_signal = True
# Optional order type mapping
order_types = {
'entry': 'market',
'exit': 'market',
'stoploss': 'market',
'stoploss_on_exchange': False
}
position_adjustment_enable = True
max_entry_position_adjustment = 2
current_loss_percent = -0.03 # profit or loss used as mark for new orders 1 = 100%, 0.1 = 10%, 0.01 = 1%
custom_base = {}
max_dca_multiplier = 6
# This is called when placing the initial order (opening trade)
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, entry_tag: Optional[str], **kwargs) -> float:
return proposed_stake / self.max_dca_multiplier
def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: float, max_stake: float, **kwargs):
"""
Custom trade adjustment logic, returning the stake amount that a trade should be increased.
This means extra buy orders with additional fees.
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
:param current_rate: Current buy rate.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param min_stake: Minimal stake size allowed by exchange.
:param max_stake: Balance available for trading.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: Stake amount to adjust your trade
"""
if current_profit > self.current_loss_percent:
return None
# Obtain pair dataframe (just to show how to access it)
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
# # Only buy when not actively falling price.
# only buy if it is actively falling!
# TODO: do some math to find better falling scheme (eg: add candle difference percent)
last_candle = dataframe.iloc[-1].squeeze()
previous_candle = dataframe.iloc[-2].squeeze()
if last_candle['close'] > previous_candle['close']:
return None
filled_buys = trade.select_filled_orders('buy')
count_of_buys = trade.nr_of_successful_buys
# # Allow up to 3 additional increasingly larger buys (4 in total)
# # Initial buy is 1x
# # If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2%
# # If that falls down to -5% again, we buy 1.5x more
# # If that falls once again down to -5%, we buy 1.75x more
# # Total stake for this trade would be 1 + 1.25 + 1.5 + 1.75 = 5.5x of the initial allowed stake.
# # That is why max_dca_multiplier is 5.5
# # Hope you have a deep wallet!
try:
# This returns first order stake size
stake_amount = filled_buys[0].cost
# This then calculates current safety order size
# stake_amount = stake_amount * (1 + (count_of_buys * 0.25))
stake_amount = stake_amount * (1 + count_of_buys)
return stake_amount
except Exception as exception:
return None
return None
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
"""
dataframe["LL"] = ta.MIN(dataframe["low"], timeperiod=28) # find the lowest in timeperiod
dataframe["confirm"] = ta.MIN(dataframe["low"], timeperiod=7) # check if latest N candles are higher than lowestlow
dataframe["base"] = dataframe.loc[(dataframe["LL"] == dataframe["confirm"]) & (dataframe["LL"] == dataframe["LL"].shift(-7)), ["LL"]]
dft = dataframe[['date','base']].copy()
# dft.dropna(how='any', inplace=True)
# dft.drop_duplicates(subset=['base'],keep='last', inplace=True)
dft['cracked'] = np.NaN
dft['closed'] = np.NaN
for i in dft.itertuples():
if np.isnan(i.cracked):
dft.loc[((dataframe['date'] > i.date) & (dataframe['open'] > i.base) & (dataframe['close'] < i.base)), 'cracked'] = i.base
dft.loc[dft['cracked'].duplicated(), 'cracked'] = np.nan
for i in dft.itertuples():
if np.isnan(i.closed):
dft.loc[((dataframe['date'] > i.date) & (dataframe['open'] < dataframe['close']) & (dataframe['open'] <= i.cracked) & (dataframe['close'] >= i.cracked)), 'closed'] = i.cracked
dft.loc[dft['closed'].duplicated(), 'closed'] = np.nan
dataframe = dataframe.join(dft[['cracked', 'closed']], how='outer')
# dataframe.fillna(method='ffill', inplace=True)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[((dataframe['open'] > dataframe['cracked']) & (dataframe['close'] < dataframe['cracked'])), 'buy'] = 1
return dataframe
# def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, entry_tag: Optional[str], **kwargs) -> float:
# dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,timeframe=self.timeframe)
# new_entryprice = dataframe['base'].iat[-1]
# return new_entryprice
# def custom_exit_price(self, pair: str, trade: Trade, current_time: datetime, proposed_rate: float, current_profit: float, **kwargs) -> float:
# dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
# # get the current candle
# current_candle = dataframe.iloc[-1].squeeze()
# if current_candle['high'] >= current_candle['base_closed']:
# return 'sell_at_base'
# return None
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[(dataframe['open'] < dataframe['closed']) & (dataframe['close'] > dataframe['closed']), 'sell'] = 1
return dataframe