This commit is contained in:
Gert Wohlgemuth
2018-06-02 03:21:07 +00:00
committed by GitHub
15 changed files with 453 additions and 9 deletions

View File

@@ -14,7 +14,6 @@ from freqtrade.exchange import get_ticker_history
from freqtrade.persistence import Trade
from freqtrade.strategy.resolver import StrategyResolver
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
buying or selling.
"""
def __init__(self, config: dict) -> None:
"""
Init Analyze
@@ -195,10 +195,41 @@ class Analyze(object):
:return True if bot should sell at 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.')
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
time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60
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)
close_date = Column(DateTime)
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):
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'
)
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:
"""
Updates this entity with amount and actual open/close rates.

View File

@@ -98,10 +98,11 @@ class RPC(object):
trade.id,
trade.pair,
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 = df_statuses.set_index(columns[0])
# 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:
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = Backtesting(config)
data = load_data_test(contour)
@@ -97,6 +98,7 @@ def simple_backtest(config, contour, num_results, mocker) -> None:
'realistic': True
}
)
# results :: <class 'pandas.core.frame.DataFrame'>
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 'ETH/BTC' in result['Pair'].all()
assert '-0.59%' in result['Profit'].all()
assert 'Value' in result
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):
resolver = StrategyResolver()
extra_dir = os.path.join('some', 'path')
with pytest.raises(
FileNotFoundError,
match=r".*No such file or directory: '{}'".format(extra_dir)):
resolver._load_strategy('TestStrategy', extra_dir)
if os.name == 'nt':
with pytest.raises(
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 'adx' in resolver.strategy.populate_indicators(result)

View File

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