This commit is contained in:
Gert Wohlgemuth 2018-06-02 03:21:07 +00:00 committed by GitHub
commit 1688fba2f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 453 additions and 9 deletions

1
.gitignore vendored
View File

@ -6,7 +6,6 @@ config*.json
.hyperopt .hyperopt
logfile.txt logfile.txt
hyperopt_trials.pickle hyperopt_trials.pickle
user_data/
freqtrade-plot.html freqtrade-plot.html
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View File

@ -5,6 +5,9 @@
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"ticker_interval" : "5m", "ticker_interval" : "5m",
"dry_run": false, "dry_run": false,
"trailing_stop": {
"positive" : 0.005
},
"unfilledtimeout": 600, "unfilledtimeout": 600,
"bid_strategy": { "bid_strategy": {
"ask_last_balance": 0.0 "ask_last_balance": 0.0

View File

@ -5,6 +5,7 @@
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"dry_run": false, "dry_run": false,
"ticker_interval": "5m", "ticker_interval": "5m",
"trailing_stop": true,
"minimal_roi": { "minimal_roi": {
"40": 0.0, "40": 0.0,
"30": 0.01, "30": 0.01,

View File

@ -14,7 +14,6 @@ from freqtrade.exchange import get_ticker_history
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.strategy.resolver import StrategyResolver from freqtrade.strategy.resolver import StrategyResolver
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -31,6 +30,7 @@ class Analyze(object):
Analyze class contains everything the bot need to determine if the situation is good for Analyze class contains everything the bot need to determine if the situation is good for
buying or selling. buying or selling.
""" """
def __init__(self, config: dict) -> None: def __init__(self, config: dict) -> None:
""" """
Init Analyze Init Analyze
@ -195,10 +195,41 @@ class Analyze(object):
:return True if bot should sell at current rate :return True if bot should sell at current rate
""" """
current_profit = trade.calc_profit_percent(current_rate) current_profit = trade.calc_profit_percent(current_rate)
if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss:
if trade.stop_loss is None:
# initially adjust the stop loss to the base value
trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss)
# evaluate if the stoploss was hit
if self.strategy.stoploss is not None and trade.stop_loss >= current_rate:
if 'trailing_stop' in self.config and self.config['trailing_stop']:
logger.warning(
"HIT STOP: current price at {:.6f}, stop loss is {:.6f}, "
"initial stop loss was at {:.6f}, trade opened at {:.6f}".format(
current_rate, trade.stop_loss, trade.initial_stop_loss, trade.open_rate))
logger.debug("trailing stop saved us: {:.6f}"
.format(trade.stop_loss - trade.initial_stop_loss))
logger.debug('Stop loss hit.') logger.debug('Stop loss hit.')
return True return True
# update the stop loss afterwards, after all by definition it's supposed to be hanging
if 'trailing_stop' in self.config and self.config['trailing_stop']:
# check if we have a special stop loss for positive condition
# and if profit is positive
stop_loss_value = self.strategy.stoploss
if isinstance(self.config['trailing_stop'], dict) and \
'positive' in self.config['trailing_stop'] and \
current_profit > 0:
logger.debug("using positive stop loss mode: {} since we have profit {}".format(
self.config['trailing_stop']['positive'], current_profit))
stop_loss_value = self.config['trailing_stop']['positive']
trade.adjust_stop_loss(current_rate, stop_loss_value)
# Check if time matches and current rate is above threshold # Check if time matches and current rate is above threshold
time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60 time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60
for duration, threshold in self.strategy.minimal_roi.items(): for duration, threshold in self.strategy.minimal_roi.items():

View File

@ -148,6 +148,12 @@ class Trade(_DECL_BASE):
open_date = Column(DateTime, nullable=False, default=datetime.utcnow) open_date = Column(DateTime, nullable=False, default=datetime.utcnow)
close_date = Column(DateTime) close_date = Column(DateTime)
open_order_id = Column(String) open_order_id = Column(String)
# absolute value of the stop loss
stop_loss = Column(Float, nullable=True, default=0.0)
# absolute value of the initial stop loss
initial_stop_loss = Column(Float, nullable=True, default=0.0)
# absolute value of the highest reached price
max_rate = Column(Float, nullable=True, default=0.0)
def __repr__(self): def __repr__(self):
return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format( return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format(
@ -158,6 +164,50 @@ class Trade(_DECL_BASE):
arrow.get(self.open_date).humanize() if self.is_open else 'closed' arrow.get(self.open_date).humanize() if self.is_open else 'closed'
) )
def adjust_stop_loss(self, current_price, stoploss):
"""
this adjusts the stop loss to it's most recently observed
setting
:param current_price:
:param stoploss:
:return:
"""
new_loss = Decimal(current_price * (1 - abs(stoploss)))
# keeping track of the highest observed rate for this trade
if self.max_rate is None:
self.max_rate = current_price
else:
if current_price > self.max_rate:
self.max_rate = current_price
# no stop loss assigned yet
if self.stop_loss is None or self.stop_loss == 0:
logger.debug("assigning new stop loss")
self.stop_loss = new_loss
self.initial_stop_loss = new_loss
# evaluate if the stop loss needs to be updated
else:
if new_loss > self.stop_loss: # stop losses only walk up, never down!
self.stop_loss = new_loss
logger.debug("adjusted stop loss")
else:
logger.debug("keeping current stop loss")
logger.debug(
"{} - current price {:.8f}, bought at {:.8f} and calculated "
"stop loss is at: {:.8f} initial stop at {:.8f}. trailing stop loss saved us: {:.8f} "
"and max observed rate was {:.8f}".format(
self.pair, current_price, self.open_rate,
self.initial_stop_loss,
self.stop_loss, float(self.stop_loss) - float(self.initial_stop_loss),
self.max_rate
))
def update(self, order: Dict) -> None: def update(self, order: Dict) -> None:
""" """
Updates this entity with amount and actual open/close rates. Updates this entity with amount and actual open/close rates.

View File

@ -98,10 +98,11 @@ class RPC(object):
trade.id, trade.id,
trade.pair, trade.pair,
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
'{:.2f}%'.format(100 * trade.calc_profit_percent(current_rate)) '{:.2f}%'.format(100 * trade.calc_profit_percent(current_rate)),
'{:.6f}'.format(trade.amount * current_rate)
]) ])
columns = ['ID', 'Pair', 'Since', 'Profit'] columns = ['ID', 'Pair', 'Since', 'Profit', 'Value']
df_statuses = DataFrame.from_records(trades_list, columns=columns) df_statuses = DataFrame.from_records(trades_list, columns=columns)
df_statuses = df_statuses.set_index(columns[0]) df_statuses = df_statuses.set_index(columns[0])
# The style used throughout is to return a tuple # The style used throughout is to return a tuple

View File

@ -84,6 +84,7 @@ def load_data_test(what):
def simple_backtest(config, contour, num_results, mocker) -> None: def simple_backtest(config, contour, num_results, mocker) -> None:
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = Backtesting(config) backtesting = Backtesting(config)
data = load_data_test(contour) data = load_data_test(contour)
@ -97,6 +98,7 @@ def simple_backtest(config, contour, num_results, mocker) -> None:
'realistic': True 'realistic': True
} }
) )
# results :: <class 'pandas.core.frame.DataFrame'> # results :: <class 'pandas.core.frame.DataFrame'>
assert len(results) == num_results assert len(results) == num_results

View File

@ -106,6 +106,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert 'just now' in result['Since'].all() assert 'just now' in result['Since'].all()
assert 'ETH/BTC' in result['Pair'].all() assert 'ETH/BTC' in result['Pair'].all()
assert '-0.59%' in result['Profit'].all() assert '-0.59%' in result['Profit'].all()
assert 'Value' in result
def test_rpc_daily_profit(default_conf, update, ticker, fee, def test_rpc_daily_profit(default_conf, update, ticker, fee,

View File

@ -29,10 +29,18 @@ def test_load_strategy(result):
def test_load_strategy_custom_directory(result): def test_load_strategy_custom_directory(result):
resolver = StrategyResolver() resolver = StrategyResolver()
extra_dir = os.path.join('some', 'path') extra_dir = os.path.join('some', 'path')
with pytest.raises(
FileNotFoundError, if os.name == 'nt':
match=r".*No such file or directory: '{}'".format(extra_dir)): with pytest.raises(
resolver._load_strategy('TestStrategy', extra_dir) FileNotFoundError,
match="FileNotFoundError: [WinError 3] The system cannot find the "
"path specified: '{}'".format(extra_dir)):
resolver._load_strategy('TestStrategy', extra_dir)
else:
with pytest.raises(
FileNotFoundError,
match=r".*No such file or directory: '{}'".format(extra_dir)):
resolver._load_strategy('TestStrategy', extra_dir)
assert hasattr(resolver.strategy, 'populate_indicators') assert hasattr(resolver.strategy, 'populate_indicators')
assert 'adx' in resolver.strategy.populate_indicators(result) assert 'adx' in resolver.strategy.populate_indicators(result)

View File

@ -444,6 +444,8 @@ def test_migrate_new(default_conf, fee):
close_profit FLOAT, close_profit FLOAT,
stake_amount FLOAT NOT NULL, stake_amount FLOAT NOT NULL,
amount FLOAT, amount FLOAT,
initial_stop_loss FLOAT,
max_rate FLOAT,
open_date DATETIME NOT NULL, open_date DATETIME NOT NULL,
close_date DATETIME, close_date DATETIME,
open_order_id VARCHAR, open_order_id VARCHAR,

View File

@ -159,6 +159,15 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
fillcolor="rgba(0,176,246,0.2)", fillcolor="rgba(0,176,246,0.2)",
line={'color': "transparent"}, line={'color': "transparent"},
) )
bb_middle = go.Scatter(
x=data.date,
y=data.bb_middleband,
name='BB middle',
fill="tonexty",
fillcolor="rgba(0,176,246,0.2)",
line={'color': "red"},
)
macd = go.Scattergl(x=data['date'], y=data['macd'], name='MACD') macd = go.Scattergl(x=data['date'], y=data['macd'], name='MACD')
macdsignal = go.Scattergl(x=data['date'], y=data['macdsignal'], name='MACD signal') macdsignal = go.Scattergl(x=data['date'], y=data['macdsignal'], name='MACD signal')
volume = go.Bar(x=data['date'], y=data['volume'], name='Volume') volume = go.Bar(x=data['date'], y=data['volume'], name='Volume')
@ -173,7 +182,9 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
fig.append_trace(candles, 1, 1) fig.append_trace(candles, 1, 1)
fig.append_trace(bb_lower, 1, 1) fig.append_trace(bb_lower, 1, 1)
fig.append_trace(bb_middle, 1, 1)
fig.append_trace(bb_upper, 1, 1) fig.append_trace(bb_upper, 1, 1)
fig.append_trace(buys, 1, 1) fig.append_trace(buys, 1, 1)
fig.append_trace(sells, 1, 1) fig.append_trace(sells, 1, 1)
fig.append_trace(volume, 2, 1) fig.append_trace(volume, 2, 1)

View File

@ -0,0 +1,94 @@
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from hyperopt import hp
from functools import reduce
from pandas import DataFrame
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy # noqa
class Long(IStrategy):
"""
author@: Gert Wohlgemuth
"""
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = {
"60": 0.05,
"30": 0.06,
"20": 0.07,
"0": 0.08
}
# Optimal stoploss designed for the strategy
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.15
# Optimal ticker interval for the strategy
ticker_interval = 60
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
dataframe['cci'] = ta.CCI(dataframe)
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=50)
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
rsi = 0.1 * (dataframe['rsi'] - 50)
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
# SAR Parabol
dataframe['sar'] = ta.SAR(dataframe)
return dataframe
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['macd'] > dataframe['macdsignal']) &
(dataframe['macd'] > 0) &
(dataframe['cci'] <= 0.0)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
# (dataframe['tema'] < dataframe['close'])
(dataframe['sar'] > dataframe['close']) &
(dataframe['fisher_rsi'] > 0.3)
),
'sell'] = 1
return dataframe

View File

@ -0,0 +1,75 @@
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from hyperopt import hp
from functools import reduce
from pandas import DataFrame
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class Quickie(IStrategy):
"""
author@: Gert Wohlgemuth
idea:
momentum based strategie. The main idea is that it closes trades very quickly, while avoiding excessive losses. Hence a rather moderate stop loss in this case
"""
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = {
"60": 0.005,
"10": 0.01,
}
# Optimal stoploss designed for the strategy
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.25
# Optimal ticker interval for the strategy
ticker_interval = 5
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
dataframe['adx'] = ta.ADX(dataframe)
dataframe['sma_200'] = ta.SMA(dataframe, timeperiod=200)
dataframe['sma_50'] = ta.SMA(dataframe, timeperiod=50)
# required for graphing
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
return dataframe
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
dataframe.loc[
(
(
(dataframe['adx'] > 30) &
(dataframe['tema'] < dataframe['bb_middleband']) &
(dataframe['tema'] > dataframe['tema'].shift(1)) &
(dataframe['sma_200'] > dataframe['close'])
)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
dataframe.loc[
(
(
(dataframe['adx'] > 70) &
(dataframe['tema'] > dataframe['bb_middleband']) &
(dataframe['tema'] < dataframe['tema'].shift(1))
)
),
'sell'] = 1
return dataframe

View File

@ -0,0 +1,76 @@
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from hyperopt import hp
from functools import reduce
from pandas import DataFrame
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class Simple(IStrategy):
"""
author@: Gert Wohlgemuth
idea:
this strategy is based on the book, 'The Simple Strategy' and can be found in detail here:
https://www.amazon.com/Simple-Strategy-Powerful-Trading-Futures-ebook/dp/B00E66QPCG/ref=sr_1_1?ie=UTF8&qid=1525202675&sr=8-1&keywords=the+simple+strategy
"""
# Minimal ROI designed for the strategy.
# since this strategy is planned around 5 minutes, we assume any time we have a 5% profit we should call it a day
# This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = {
"0": 0.01
}
# Optimal stoploss designed for the strategy
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.25
# Optimal ticker interval for the strategy
ticker_interval = 5
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)
# required for graphing
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=12, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['bb_middleband'] = bollinger['mid']
return dataframe
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
dataframe.loc[
(
(
(dataframe['macd'] > 0) # over 0
& (dataframe['macd'] > dataframe['macdsignal']) # over signal
& (dataframe['bb_upperband'] > dataframe['bb_upperband'].shift(1)) # pointed up
& (dataframe['rsi'] > 70) # optional filter, need to investigate
)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
# different strategy used for sell points, due to be able to duplicate it to 100%
dataframe.loc[
(
(dataframe['rsi'] > 80)
),
'sell'] = 1
return dataframe

View File

@ -0,0 +1,90 @@
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from hyperopt import hp
from functools import reduce
from pandas import DataFrame
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class ZLC(IStrategy):
"""
author@: Gert Wohlgemuth
"""
# 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.01
}
# Optimal stoploss designed for the strategy
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.3
# Optimal ticker interval for the strategy
ticker_interval = 5
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
dataframe['cci-slow'] = ta.CCI(dataframe, timeperiod=25)
dataframe['cci-fast'] = ta.CCI(dataframe, timeperiod=50)
dataframe['expo'] = ta.EMA(dataframe, timeperiod=35)
# required for graphing
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
return dataframe
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
#don't buy on peak tops
(dataframe['close'] < dataframe['bb_middleband'])
# this is the main concept of evaluating buys
& (dataframe['cci-fast'] > 0)
& (dataframe['cci-slow'] > 0)
& (dataframe['close'] > dataframe['expo'])
)
,
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(dataframe['close'] >= dataframe['bb_upperband']) |
(
(dataframe['cci-fast'] < 0)
& (dataframe['cci-slow'] < 0)
& (dataframe['close'] < dataframe['expo'])
)
,
'sell'] = 0
return dataframe