uncomplex backtest
This commit is contained in:
parent
feb5da0c35
commit
27769f0301
@ -68,17 +68,59 @@ def generate_text_table(
|
|||||||
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt)
|
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt)
|
||||||
|
|
||||||
|
|
||||||
def backtest(stake_amount: float, processed: Dict[str, DataFrame],
|
def get_trade_entry(pair, row, ticker, trade_count_lock, args):
|
||||||
max_open_trades: int = 0, realistic: bool = True, sell_profit_only: bool = False,
|
stake_amount = args['stake_amount']
|
||||||
stoploss: int = -1.00, use_sell_signal: bool = False) -> DataFrame:
|
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,
|
||||||
|
amount=stake_amount / row.open,
|
||||||
|
fee=exchange.get_fee()
|
||||||
|
)
|
||||||
|
|
||||||
|
# calculate win/lose forwards from buy point
|
||||||
|
sell_subset = ticker[row.Index + 1:][['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)
|
||||||
|
|
||||||
|
return row2.Index, (pair,
|
||||||
|
current_profit_percent,
|
||||||
|
current_profit_btc,
|
||||||
|
row2.Index - row.Index,
|
||||||
|
current_profit_btc > 0,
|
||||||
|
current_profit_btc < 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def backtest(args) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Implements backtesting functionality
|
Implements backtesting functionality
|
||||||
:param stake_amount: btc amount to use for each trade
|
:param args: a dict containing:
|
||||||
:param processed: a processed dictionary with format {pair, data}
|
stake_amount: btc amount to use for each trade
|
||||||
:param max_open_trades: maximum number of concurrent trades (default: 0, disabled)
|
processed: a processed dictionary with format {pair, data}
|
||||||
:param realistic: do we try to simulate realistic trades? (default: True)
|
max_open_trades: maximum number of concurrent trades (default: 0, disabled)
|
||||||
|
realistic: do we try to simulate realistic trades? (default: True)
|
||||||
|
sell_profit_only: sell if profit only
|
||||||
|
use_sell_signal: act on sell-signal
|
||||||
|
stoploss: use stoploss
|
||||||
:return: DataFrame
|
:return: DataFrame
|
||||||
"""
|
"""
|
||||||
|
processed = args['processed']
|
||||||
|
max_open_trades = args.get('max_open_trades', 0)
|
||||||
|
realistic = args.get('realistic', True)
|
||||||
trades = []
|
trades = []
|
||||||
trade_count_lock: dict = {}
|
trade_count_lock: dict = {}
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = Bittrex({'key': '', 'secret': ''})
|
||||||
@ -101,41 +143,11 @@ def backtest(stake_amount: float, processed: Dict[str, 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
|
||||||
|
|
||||||
trade = Trade(
|
ret = get_trade_entry(pair, row, ticker,
|
||||||
open_rate=row.close,
|
trade_count_lock, args)
|
||||||
open_date=row.date,
|
if ret:
|
||||||
stake_amount=stake_amount,
|
lock_pair_until, trade_entry = ret
|
||||||
amount=stake_amount / row.open,
|
trades.append(trade_entry)
|
||||||
fee=exchange.get_fee()
|
|
||||||
)
|
|
||||||
|
|
||||||
# calculate win/lose forwards from buy point
|
|
||||||
sell_subset = ticker[row.Index + 1:][['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)
|
|
||||||
lock_pair_until = row2.Index
|
|
||||||
|
|
||||||
trades.append(
|
|
||||||
(
|
|
||||||
pair,
|
|
||||||
current_profit_percent,
|
|
||||||
current_profit_btc,
|
|
||||||
row2.Index - row.Index,
|
|
||||||
current_profit_btc > 0,
|
|
||||||
current_profit_btc < 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
break
|
|
||||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration', 'profit', 'loss']
|
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration', 'profit', 'loss']
|
||||||
return DataFrame.from_records(trades, columns=labels)
|
return DataFrame.from_records(trades, columns=labels)
|
||||||
|
|
||||||
@ -181,17 +193,17 @@ def start(args):
|
|||||||
# Print timeframe
|
# Print timeframe
|
||||||
min_date, max_date = get_timeframe(preprocessed)
|
min_date, max_date = get_timeframe(preprocessed)
|
||||||
logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat())
|
logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat())
|
||||||
|
|
||||||
# Execute backtest and print results
|
# Execute backtest and print results
|
||||||
results = backtest(
|
sell_profit_only = config.get('experimental', {}).get('sell_profit_only', False)
|
||||||
stake_amount=config['stake_amount'],
|
use_sell_signal = config.get('experimental', {}).get('use_sell_signal', False)
|
||||||
processed=preprocessed,
|
results = backtest({'stake_amount': config['stake_amount'],
|
||||||
max_open_trades=max_open_trades,
|
'processed': preprocessed,
|
||||||
realistic=args.realistic_simulation,
|
'max_open_trades': max_open_trades,
|
||||||
sell_profit_only=config.get('experimental', {}).get('sell_profit_only', False),
|
'realistic': args.realistic_simulation,
|
||||||
stoploss=config.get('stoploss'),
|
'sell_profit_only': sell_profit_only,
|
||||||
use_sell_signal=config.get('experimental', {}).get('use_sell_signal', False)
|
'use_sell_signal': use_sell_signal,
|
||||||
)
|
'stoploss': config.get('stoploss')
|
||||||
|
})
|
||||||
logger.info(
|
logger.info(
|
||||||
'\n==================================== BACKTESTING REPORT ====================================\n%s', # noqa
|
'\n==================================== BACKTESTING REPORT ====================================\n%s', # noqa
|
||||||
generate_text_table(data, results, config['stake_currency'], args.ticker_interval)
|
generate_text_table(data, results, config['stake_currency'], args.ticker_interval)
|
||||||
|
@ -128,7 +128,9 @@ def optimizer(params):
|
|||||||
from freqtrade.optimize import backtesting
|
from freqtrade.optimize import backtesting
|
||||||
backtesting.populate_buy_trend = buy_strategy_generator(params)
|
backtesting.populate_buy_trend = buy_strategy_generator(params)
|
||||||
|
|
||||||
results = backtest(OPTIMIZE_CONFIG['stake_amount'], PROCESSED, stoploss=params['stoploss'])
|
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
|
||||||
|
'processed': PROCESSED,
|
||||||
|
'stoploss': params['stoploss']})
|
||||||
result_explanation = format_results(results)
|
result_explanation = format_results(results)
|
||||||
|
|
||||||
total_profit = results.profit_percent.sum()
|
total_profit = results.profit_percent.sum()
|
||||||
|
@ -43,8 +43,10 @@ def test_backtest(default_conf, mocker):
|
|||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = Bittrex({'key': '', 'secret': ''})
|
||||||
|
|
||||||
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
|
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
|
||||||
results = backtest(default_conf['stake_amount'],
|
results = backtest({'stake_amount': default_conf['stake_amount'],
|
||||||
optimize.preprocess(data), 10, True)
|
'processed': optimize.preprocess(data),
|
||||||
|
'max_open_trades': 10,
|
||||||
|
'realistic': True})
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
|
||||||
|
|
||||||
@ -54,8 +56,10 @@ def test_backtest_1min_ticker_interval(default_conf, mocker):
|
|||||||
|
|
||||||
# Run a backtesting for an exiting 5min ticker_interval
|
# Run a backtesting for an exiting 5min ticker_interval
|
||||||
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'])
|
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'])
|
||||||
results = backtest(default_conf['stake_amount'],
|
results = backtest({'stake_amount': default_conf['stake_amount'],
|
||||||
optimize.preprocess(data), 1, True)
|
'processed': optimize.preprocess(data),
|
||||||
|
'max_open_trades': 1,
|
||||||
|
'realistic': True})
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
|
||||||
|
|
||||||
@ -113,7 +117,10 @@ def simple_backtest(config, contour, num_results):
|
|||||||
data = load_data_test(contour)
|
data = load_data_test(contour)
|
||||||
processed = optimize.preprocess(data)
|
processed = optimize.preprocess(data)
|
||||||
assert isinstance(processed, dict)
|
assert isinstance(processed, dict)
|
||||||
results = backtest(config['stake_amount'], processed, 1, True)
|
results = backtest({'stake_amount': config['stake_amount'],
|
||||||
|
'processed': processed,
|
||||||
|
'max_open_trades': 1,
|
||||||
|
'realistic': True})
|
||||||
# results :: <class 'pandas.core.frame.DataFrame'>
|
# results :: <class 'pandas.core.frame.DataFrame'>
|
||||||
assert len(results) == num_results
|
assert len(results) == num_results
|
||||||
|
|
||||||
@ -125,8 +132,10 @@ def simple_backtest(config, contour, num_results):
|
|||||||
def test_backtest2(default_conf, mocker):
|
def test_backtest2(default_conf, mocker):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
|
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
|
||||||
results = backtest(default_conf['stake_amount'],
|
results = backtest({'stake_amount': default_conf['stake_amount'],
|
||||||
optimize.preprocess(data), 10, True)
|
'processed': optimize.preprocess(data),
|
||||||
|
'max_open_trades': 10,
|
||||||
|
'realistic': True})
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user