Timeframe
5m
Direction
Long Only
Stoploss
-8.5%
Trailing Stop
No
ROI
0m: 15.0%, 5m: 2.5%, 10m: 1.5%
Interface Version
N/A
Startup Candles
N/A
Indicators
3
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np
import talib.abstract as ta
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import merge_informative_pair
from pandas import DataFrame, Series
from functools import reduce
from datetime import datetime
from freqtrade.persistence import Trade
from technical.indicators import RMI
"""
TODO:
- Better buy signal.
"""
class Hacklemore(IStrategy):
timeframe = '5m'
# ROI table:
minimal_roi = {
"0": 0.15,
"5": 0.025,
"10": 0.015
}
# Stoploss:
stoploss = -0.085
use_sell_signal = True
sell_profit_only = False
ignore_roi_if_buy_signal = True
startup_candle_count: int = 24
custom_trade_info = {}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Misc calculations regarding existing open positions
if self.config['runmode'].value in ('live', 'dry_run'):
self.custom_trade_info[metadata['pair']] = trade_data = {}
trade_data['active_trade'] = trade_data['other_trades'] = False
active_trade = Trade.get_trades([Trade.pair == metadata['pair'], Trade.is_open.is_(True),]).all()
other_trades = Trade.get_trades([Trade.pair != metadata['pair'], Trade.is_open.is_(True),]).all()
if active_trade:
trade_data['active_trade'] = True
trade_data['current_profit'] = active_trade[0].calc_profit_ratio(rate=self.get_current_price(metadata['pair']))
trade_data['peak_profit'] = active_trade[0].calc_profit_ratio(rate=active_trade[0].max_rate)
trade_data['current_peak_ratio'] = (trade_data['current_profit'] / trade_data['peak_profit'])
if other_trades:
trade_data['other_trades'] = True
total_other_profit = sum(trade.calc_profit_ratio(rate=self.get_current_price(trade.pair)) for trade in other_trades)
trade_data['avg_other_profit'] = total_other_profit / len(other_trades)
self.custom_trade_info[metadata['pair']] = trade_data
# Set up other indicators
dataframe['volume_mean_slow'] = dataframe['volume'].rolling(window=24).mean()
dataframe['rmi-slow'] = RMI(dataframe, length=20, mom=5)
dataframe['rmi-fast'] = RMI(dataframe, length=9, mom=3)
dataframe['sar'] = ta.SAR(dataframe)
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# Trend calculations
dataframe['max'] = dataframe['high'].rolling(12).max()
dataframe['min'] = dataframe['low'].rolling(12).min()
dataframe['upper'] = np.where(dataframe['max'] > dataframe['max'].shift(),1,0)
dataframe['lower'] = np.where(dataframe['min'] < dataframe['min'].shift(),1,0)
dataframe['up_trend'] = np.where(dataframe['upper'].rolling(3, min_periods=1).sum() != 0,1,0)
dataframe['dn_trend'] = np.where(dataframe['lower'].rolling(3, min_periods=1).sum() != 0,1,0)
# Set Up Bollinger Bands
upper_bb, mid_bb, lower_bb = ta.BBANDS(dataframe['close'], timeperiod=20)
dataframe['upper-bb'] = upper_bb
dataframe['lower-bb'] = lower_bb
dataframe['mid-bb'] = mid_bb
# did candle low break-out of bottom bband?
dataframe['breakout-low'] = np.where(dataframe['low'] <= dataframe['lower-bb'], True, False)
last = np.nan
for i in range(len(dataframe)):
if dataframe.loc[i, 'breakout-low']:
last = i
dataframe.loc[i, 'breakout-low-last'] = i - last
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
trade_data = self.custom_trade_info[metadata['pair']]
conditions = []
# Persist a buy signal for existing trades to make use of ignore_roi_if_buy_signal = True
# when this buy signal is not present a sell can happen according to ROI table
if trade_data['active_trade']:
conditions.append(trade_data['current_peak_ratio'] > 0.8)
conditions.append(dataframe['rmi-slow'] >= 60)
# Normal buy triggers that apply to new trades we want to enter
else:
conditions.append(
(dataframe['up_trend'] == 1) &
(dataframe['rmi-slow'] >= 55) &
(dataframe['macdhist'] > 0) &
(dataframe['breakout-low-last'] >= 2) &
(dataframe['breakout-low-last'] <= 10) &
(dataframe['volume'] < (dataframe['volume_mean_slow'].shift(1) * 30)) &
(dataframe['macd'] > dataframe['macdsignal']) &
(dataframe['rmi-slow'] >= dataframe['rmi-slow'].rolling(3).mean()) &
(dataframe['close'] > dataframe['close'].shift()) &
(dataframe['close'].shift() > dataframe['close'].shift(2)) &
(dataframe['sar'] < dataframe['close']) &
(dataframe['sar'].shift() < dataframe['close'].shift())
)
# if there are other open trades consider their performance before we enter a new position
if trade_data['other_trades']:
conditions.append(trade_data['avg_other_profit'] >= 0)
# applies to both new buys and persisting buy signal
conditions.append(dataframe['volume'].gt(0))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
trade_data = self.custom_trade_info[metadata['pair']]
conditions = []
# if we are in an active trade for this pair
if trade_data['active_trade']:
# if we are at a loss, consider what the trend looks and preempt the stoploss
conditions.append(
(trade_data['current_profit'] < 0) &
(trade_data['current_profit'] > self.stoploss) &
(dataframe['dn_trend'] == 1) &
(dataframe['rmi-fast'] < 50) &
(dataframe['volume'].gt(0))
# custom sell-reason: dynamic-stop-loss
)
# if there are other open trades in addition to this one, consider the average profit
# across them all (not including this one), don't sell if entire market is down big and wait for recovery
if trade_data['other_trades']:
conditions.append(trade_data['avg_other_profit'] >= -0.005)
# the bot comes through this loop even when there isn't an open trade to sell
# so we pass an impossible condiiton here because we don't want a sell signal
# clogging up the charts and not having one leads the bot to crash
else:
conditions.append(dataframe['volume'].lt(0))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe
"""
Custom methods
"""
def get_current_price(self, pair: str, side="asks") -> float:
ob = self.dp.orderbook(pair, 1)
current_price = ob[side][0][0]
return current_price
"""
Price protection on trade entry and timeouts, built-in Freqtrade functionality
https://www.freqtrade.io/en/latest/strategy-advanced/
"""
def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1)
current_price = ob['bids'][0][0]
# Cancel buy order if price is more than 1% above the order.
if current_price > order['price'] * 1.01:
return True
return False
def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1)
current_price = ob['asks'][0][0]
# Cancel sell order if price is more than 1% below the order.
if current_price < order['price'] * 0.99:
return True
return False
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1)
current_price = ob['asks'][0][0]
# Cancel buy order if price is more than 1% above the order.
if current_price > rate * 1.01:
return False
return True