From 94172091ae54ca0754debc6dcc9c9ed4dc07c730 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Mon, 29 Jan 2018 10:10:19 +0100 Subject: [PATCH] Refactoring the sell conditions evaluation to share the function with backtesting --- freqtrade/main.py | 39 ++++++++++++++--------- freqtrade/optimize/backtesting.py | 52 +++++++++++++------------------ 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index efcb27543..c4b7e0361 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -307,6 +307,30 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) - return False +def should_sell(trade: Trade, rate: float, date: datetime, buy: bool, sell: bool) -> bool: + """ + This function evaluate if on the condition required to trigger a sell has been reached + if the threshold is reached and updates the trade record. + :return: True if trade should be sold, False otherwise + """ + # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) + if min_roi_reached(trade, rate, date): + logger.debug('Executing sell due to ROI ...') + return True + + # Experimental: Check if the trade is profitable before selling it (avoid selling at loss) + if _CONF.get('experimental', {}).get('sell_profit_only', False): + logger.debug('Checking if trade is profitable ...') + if not buy and trade.calc_profit(rate=rate) <= 0: + return False + + if sell and not buy and _CONF.get('experimental', {}).get('use_sell_signal', False): + logger.debug('Executing sell due to sell signal ...') + return True + + return False + + def handle_trade(trade: Trade, interval: int) -> bool: """ Sells the current pair if the threshold is reached and updates the trade record. @@ -323,20 +347,7 @@ def handle_trade(trade: Trade, interval: int) -> bool: if _CONF.get('experimental', {}).get('use_sell_signal'): (buy, sell) = get_signal(trade.pair, interval) - # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) - if not buy and min_roi_reached(trade, current_rate, datetime.utcnow()): - logger.debug('Executing sell due to ROI ...') - execute_sell(trade, current_rate) - return True - - # Experimental: Check if the trade is profitable before selling it (avoid selling at loss) - if _CONF.get('experimental', {}).get('sell_profit_only', False): - logger.debug('Checking if trade is profitable ...') - if not buy and trade.calc_profit(rate=current_rate) <= 0: - return False - - if sell and not buy: - logger.debug('Executing sell due to sell signal ...') + if should_sell(trade, current_rate, datetime.utcnow(), buy, sell): execute_sell(trade, current_rate) return True diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a78c9e197..e8fcec875 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -12,7 +12,7 @@ import freqtrade.optimize as optimize from freqtrade import exchange from freqtrade.analyze import populate_buy_trend, populate_sell_trend from freqtrade.exchange import Bittrex -from freqtrade.main import min_roi_reached +from freqtrade.main import should_sell from freqtrade.persistence import Trade from freqtrade.strategy.strategy import Strategy @@ -50,8 +50,8 @@ def generate_text_table( result.profit_percent.mean() * 100.0, result.profit_BTC.sum(), result.duration.mean() * ticker_interval, - result.profit.sum(), - result.loss.sum() + len(result[result.profit_BTC > 0]), + len(result[result.profit_BTC < 0]) ]) # Append Total @@ -61,18 +61,15 @@ def generate_text_table( results.profit_percent.mean() * 100.0, results.profit_BTC.sum(), results.duration.mean() * ticker_interval, - results.profit.sum(), - results.loss.sum() + len(results[results.profit_BTC > 0]), + len(results[results.profit_BTC < 0]) ]) return tabulate(tabular_data, headers=headers, floatfmt=floatfmt) -def get_trade_entry(pair, row, ticker, trade_count_lock, args): +def get_sell_trade_entry(pair, row, buy_subset, ticker, trade_count_lock, args): stake_amount = args['stake_amount'] max_open_trades = args.get('max_open_trades', 0) - sell_profit_only = args.get('sell_profit_only', False) - stoploss = args.get('stoploss', -1) - use_sell_signal = args.get('use_sell_signal', False) trade = Trade(open_rate=row.close, open_date=row.date, stake_amount=stake_amount, @@ -81,26 +78,20 @@ def get_trade_entry(pair, row, ticker, trade_count_lock, args): ) # calculate win/lose forwards from buy point - sell_subset = ticker[row.Index + 1:][['close', 'date', 'sell']] + sell_subset = ticker[ticker.date > row.date][['close', 'date', 'sell']] for row2 in sell_subset.itertuples(index=True): if max_open_trades > 0: # Increase trade_count_lock for every iteration trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1 - current_profit_percent = trade.calc_profit_percent(rate=row2.close) - if (sell_profit_only and current_profit_percent < 0): - continue - if min_roi_reached(trade, row2.close, row2.date) or \ - (row2.sell == 1 and use_sell_signal) or \ - current_profit_percent <= stoploss: - current_profit_btc = trade.calc_profit(rate=row2.close) + buy_signal = buy_subset[buy_subset.date == row2.date].empty + if(should_sell(trade, row2.close, row2.date, buy_signal, row2.sell)): return row2, (pair, - current_profit_percent, - current_profit_btc, - row2.Index - row.Index, - current_profit_btc > 0, - current_profit_btc < 0 - ) + trade.calc_profit_percent(rate=row2.close), + trade.calc_profit(rate=row2.close), + row2.Index - row.Index + ), row2.date + return None def backtest(args) -> DataFrame: @@ -129,10 +120,11 @@ def backtest(args) -> DataFrame: ticker = populate_sell_trend(populate_buy_trend(pair_data)) # for each buy point lock_pair_until = None - buy_subset = ticker[ticker.buy == 1][['buy', 'open', 'close', 'date', 'sell']] + headers = ['buy', 'open', 'close', 'date', 'sell'] + buy_subset = ticker[(ticker.buy == 1) & (ticker.sell == 0)][headers] for row in buy_subset.itertuples(index=True): if realistic: - if lock_pair_until is not None and row.Index <= lock_pair_until: + if lock_pair_until is not None and row.date <= lock_pair_until: continue if max_open_trades > 0: # Check if max_open_trades has already been reached for the given date @@ -143,11 +135,11 @@ def backtest(args) -> DataFrame: # Increase lock trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 - ret = get_trade_entry(pair, row, ticker, - trade_count_lock, args) + ret = get_sell_trade_entry(pair, row, buy_subset, ticker, + trade_count_lock, args) if ret: - row2, trade_entry = ret - lock_pair_until = row2.Index + row2, trade_entry, next_date = ret + lock_pair_until = next_date trades.append(trade_entry) if record: # Note, need to be json.dump friendly @@ -162,7 +154,7 @@ def backtest(args) -> DataFrame: if record and record.find('trades') >= 0: logger.info('Dumping backtest results') misc.file_dump_json('backtest-result.json', records) - labels = ['currency', 'profit_percent', 'profit_BTC', 'duration', 'profit', 'loss'] + labels = ['currency', 'profit_percent', 'profit_BTC', 'duration'] return DataFrame.from_records(trades, columns=labels)