add BacktestresultTuple

This commit is contained in:
xmatthias 2018-06-10 13:15:25 +02:00
parent c1b2e06eda
commit 9c57d3aa8b

View File

@ -6,7 +6,7 @@ This module contains the backtesting logic
import logging import logging
import operator import operator
from argparse import Namespace from argparse import Namespace
from typing import Dict, Tuple, Any, List, Optional from typing import Dict, Tuple, Any, List, Optional, NamedTuple
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
@ -23,6 +23,18 @@ from freqtrade.persistence import Trade
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class BacktestResult(NamedTuple):
"""
NamedTuple Defining BacktestResults inputs.
"""
pair: str
profit_percent: float
profit_abs: float
open_time: float
close_time: float
trade_duration: float
class Backtesting(object): class Backtesting(object):
""" """
Backtesting class, this class contains all the logic to run a backtest Backtesting class, this class contains all the logic to run a backtest
@ -73,15 +85,15 @@ class Backtesting(object):
headers = ['pair', 'buy count', 'avg profit %', headers = ['pair', 'buy count', 'avg profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss'] 'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
for pair in data: for pair in data:
result = results[results.currency == pair] result = results[results.pair == pair]
tabular_data.append([ tabular_data.append([
pair, pair,
len(result.index), len(result.index),
result.profit_percent.mean() * 100.0, result.profit_percent.mean() * 100.0,
result.profit_BTC.sum(), result.profit_abs.sum(),
result.duration.mean(), result.trade_duration.mean(),
len(result[result.profit_BTC > 0]), len(result[result.profit_abs > 0]),
len(result[result.profit_BTC < 0]) len(result[result.profit_abs < 0])
]) ])
# Append Total # Append Total
@ -89,16 +101,16 @@ class Backtesting(object):
'TOTAL', 'TOTAL',
len(results.index), len(results.index),
results.profit_percent.mean() * 100.0, results.profit_percent.mean() * 100.0,
results.profit_BTC.sum(), results.profit_abs.sum(),
results.duration.mean(), results.trade_duration.mean(),
len(results[results.profit_BTC > 0]), len(results[results.profit_abs > 0]),
len(results[results.profit_BTC < 0]) len(results[results.profit_abs < 0])
]) ])
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe") return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
def _get_sell_trade_entry( def _get_sell_trade_entry(
self, pair: str, buy_row: DataFrame, self, pair: str, buy_row: DataFrame,
partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[Tuple]: partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]:
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)
@ -121,28 +133,27 @@ class Backtesting(object):
buy_signal = sell_row.buy buy_signal = sell_row.buy
if self.analyze.should_sell(trade, sell_row.close, sell_row.date, buy_signal, if self.analyze.should_sell(trade, sell_row.close, sell_row.date, buy_signal,
sell_row.sell): sell_row.sell):
return \
sell_row, \ return BacktestResult(pair=pair,
( profit_percent=trade.calc_profit_percent(rate=sell_row.close),
pair, profit_abs=trade.calc_profit(rate=sell_row.close),
trade.calc_profit_percent(rate=sell_row.close), open_time=buy_row.date,
trade.calc_profit(rate=sell_row.close), close_time=sell_row.date,
(sell_row.date - buy_row.date).seconds // 60 trade_duration=(sell_row.date - buy_row.date).seconds // 60
) )
if partial_ticker: if partial_ticker:
# no sell condition found - trade stil open at end of backtest period # no sell condition found - trade stil open at end of backtest period
sell_row = partial_ticker[-1] sell_row = partial_ticker[-1]
logger.info('Force_selling still open trade %s with %s perc - %s', pair, btr = BacktestResult(pair=pair,
trade.calc_profit_percent(rate=sell_row.close), profit_percent=trade.calc_profit_percent(rate=sell_row.close),
trade.calc_profit(rate=sell_row.close)) profit_abs=trade.calc_profit(rate=sell_row.close),
return \ open_time=buy_row.date,
sell_row, \ close_time=sell_row.date,
( trade_duration=(sell_row.date - buy_row.date).seconds // 60
pair,
trade.calc_profit_percent(rate=sell_row.close),
trade.calc_profit(rate=sell_row.close),
(sell_row.date - buy_row.date).seconds // 60
) )
logger.info('Force_selling still open trade %s with %s perc - %s', btr.pair,
btr.profit_percent, btr.profit_abs)
return btr
return None return None
def backtest(self, args: Dict) -> DataFrame: def backtest(self, args: Dict) -> DataFrame:
@ -202,20 +213,19 @@ class Backtesting(object):
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 = self._get_sell_trade_entry(pair, row, ticker[index + 1:], trade_entry = self._get_sell_trade_entry(pair, row, ticker[index + 1:],
trade_count_lock, args) trade_count_lock, args)
if ret: if trade_entry:
row2, trade_entry = ret lock_pair_until = trade_entry.close_time
lock_pair_until = row2.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
# record a tuple of pair, current_profit_percent, # record a tuple of pair, current_profit_percent,
# entry-date, duration # entry-date, duration
records.append((pair, trade_entry[1], records.append((pair, trade_entry.profit_percent,
row.date.strftime('%s'), trade_entry.open_time.strftime('%s'),
row2.date.strftime('%s'), trade_entry.close_time.strftime('%s'),
index, trade_entry[3])) index, trade_entry[3]))
else: else:
# Set lock_pair_until to end of testing period if trade could not be closed # Set lock_pair_until to end of testing period if trade could not be closed
@ -228,7 +238,7 @@ class Backtesting(object):
logger.info('Dumping backtest results to %s', recordfilename) logger.info('Dumping backtest results to %s', recordfilename)
file_dump_json(recordfilename, records) file_dump_json(recordfilename, records)
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration'] labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
return DataFrame.from_records(trades, columns=labels) return DataFrame.from_records(trades, columns=BacktestResult._fields)
def start(self) -> None: def start(self) -> None:
""" """