Refactoring the sell conditions evaluation to share the function with backtesting
This commit is contained in:
parent
e6c215104f
commit
94172091ae
@ -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
|
||||
|
||||
|
@ -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,
|
||||
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)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user