Refactoring the sell conditions evaluation to share the function with backtesting

This commit is contained in:
Jean-Baptiste LE STANG 2018-01-29 10:10:19 +01:00
parent e6c215104f
commit 94172091ae
2 changed files with 47 additions and 44 deletions

View File

@ -307,6 +307,30 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) -
return False 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: def handle_trade(trade: Trade, interval: int) -> bool:
""" """
Sells the current pair if the threshold is reached and updates the trade record. 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'): if _CONF.get('experimental', {}).get('use_sell_signal'):
(buy, sell) = get_signal(trade.pair, interval) (buy, sell) = get_signal(trade.pair, interval)
# Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) if should_sell(trade, current_rate, datetime.utcnow(), buy, sell):
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 ...')
execute_sell(trade, current_rate) execute_sell(trade, current_rate)
return True return True

View File

@ -12,7 +12,7 @@ import freqtrade.optimize as optimize
from freqtrade import exchange from freqtrade import exchange
from freqtrade.analyze import populate_buy_trend, populate_sell_trend from freqtrade.analyze import populate_buy_trend, populate_sell_trend
from freqtrade.exchange import Bittrex 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.persistence import Trade
from freqtrade.strategy.strategy import Strategy from freqtrade.strategy.strategy import Strategy
@ -50,8 +50,8 @@ def generate_text_table(
result.profit_percent.mean() * 100.0, result.profit_percent.mean() * 100.0,
result.profit_BTC.sum(), result.profit_BTC.sum(),
result.duration.mean() * ticker_interval, result.duration.mean() * ticker_interval,
result.profit.sum(), len(result[result.profit_BTC > 0]),
result.loss.sum() len(result[result.profit_BTC < 0])
]) ])
# Append Total # Append Total
@ -61,18 +61,15 @@ def generate_text_table(
results.profit_percent.mean() * 100.0, results.profit_percent.mean() * 100.0,
results.profit_BTC.sum(), results.profit_BTC.sum(),
results.duration.mean() * ticker_interval, results.duration.mean() * ticker_interval,
results.profit.sum(), len(results[results.profit_BTC > 0]),
results.loss.sum() len(results[results.profit_BTC < 0])
]) ])
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt) 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'] stake_amount = args['stake_amount']
max_open_trades = args.get('max_open_trades', 0) 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, trade = Trade(open_rate=row.close,
open_date=row.date, open_date=row.date,
stake_amount=stake_amount, 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 # 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): for row2 in sell_subset.itertuples(index=True):
if max_open_trades > 0: if max_open_trades > 0:
# Increase trade_count_lock for every iteration # Increase trade_count_lock for every iteration
trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1 trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1
current_profit_percent = trade.calc_profit_percent(rate=row2.close) buy_signal = buy_subset[buy_subset.date == row2.date].empty
if (sell_profit_only and current_profit_percent < 0): if(should_sell(trade, row2.close, row2.date, buy_signal, row2.sell)):
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)
return row2, (pair, return row2, (pair,
current_profit_percent, trade.calc_profit_percent(rate=row2.close),
current_profit_btc, trade.calc_profit(rate=row2.close),
row2.Index - row.Index, row2.Index - row.Index
current_profit_btc > 0, ), row2.date
current_profit_btc < 0 return None
)
def backtest(args) -> DataFrame: def backtest(args) -> DataFrame:
@ -129,10 +120,11 @@ def backtest(args) -> DataFrame:
ticker = populate_sell_trend(populate_buy_trend(pair_data)) ticker = populate_sell_trend(populate_buy_trend(pair_data))
# for each buy point # for each buy point
lock_pair_until = None 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): for row in buy_subset.itertuples(index=True):
if realistic: 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 continue
if max_open_trades > 0: if max_open_trades > 0:
# Check if max_open_trades has already been reached for the given date # Check if max_open_trades has already been reached for the given date
@ -143,11 +135,11 @@ def backtest(args) -> DataFrame:
# Increase lock # Increase lock
trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1
ret = get_trade_entry(pair, row, ticker, ret = get_sell_trade_entry(pair, row, buy_subset, ticker,
trade_count_lock, args) trade_count_lock, args)
if ret: if ret:
row2, trade_entry = ret row2, trade_entry, next_date = ret
lock_pair_until = row2.Index lock_pair_until = next_date
trades.append(trade_entry) trades.append(trade_entry)
if record: if record:
# Note, need to be json.dump friendly # Note, need to be json.dump friendly
@ -162,7 +154,7 @@ def backtest(args) -> DataFrame:
if record and record.find('trades') >= 0: if record and record.find('trades') >= 0:
logger.info('Dumping backtest results') logger.info('Dumping backtest results')
misc.file_dump_json('backtest-result.json', records) 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) return DataFrame.from_records(trades, columns=labels)