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
|
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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user