Merge branch 'develop' into move_datadownload

This commit is contained in:
Matthias
2022-08-31 10:23:45 +00:00
101 changed files with 3154 additions and 1523 deletions

View File

@@ -23,7 +23,8 @@ from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType, RunMode,
TradingMode)
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from freqtrade.exchange import (amount_to_contract_precision, price_to_precision,
timeframe_to_minutes, timeframe_to_seconds)
from freqtrade.mixins import LoggingMixin
from freqtrade.optimize.backtest_caching import get_strategy_run_id
from freqtrade.optimize.bt_progress import BTProgress
@@ -257,7 +258,7 @@ class Backtesting:
funding_rates_dict = history.load_data(
datadir=self.config['datadir'],
pairs=self.pairlists.whitelist,
timeframe=self.exchange._ft_has['mark_ohlcv_timeframe'],
timeframe=self.exchange.get_option('mark_ohlcv_timeframe'),
timerange=self.timerange,
startup_candles=0,
fail_without_data=True,
@@ -269,12 +270,12 @@ class Backtesting:
mark_rates_dict = history.load_data(
datadir=self.config['datadir'],
pairs=self.pairlists.whitelist,
timeframe=self.exchange._ft_has['mark_ohlcv_timeframe'],
timeframe=self.exchange.get_option('mark_ohlcv_timeframe'),
timerange=self.timerange,
startup_candles=0,
fail_without_data=True,
data_format=self.config.get('dataformat_ohlcv', 'json'),
candle_type=CandleType.from_string(self.exchange._ft_has["mark_ohlcv_price"])
candle_type=CandleType.from_string(self.exchange.get_option("mark_ohlcv_price"))
)
# Combine data to avoid combining the data per trade.
unavailable_pairs = []
@@ -524,12 +525,16 @@ class Backtesting:
# Check if we should increase our position
if stake_amount is not None and stake_amount > 0.0:
pos_trade = self._enter_trade(
trade.pair, row, 'short' if trade.is_short else 'long', stake_amount, trade)
if pos_trade is not None:
self.wallets.update()
return pos_trade
check_adjust_entry = True
if self.strategy.max_entry_position_adjustment > -1:
entry_count = trade.nr_of_successful_entries
check_adjust_entry = (entry_count <= self.strategy.max_entry_position_adjustment)
if check_adjust_entry:
pos_trade = self._enter_trade(
trade.pair, row, 'short' if trade.is_short else 'long', stake_amount, trade)
if pos_trade is not None:
self.wallets.update()
return pos_trade
if stake_amount is not None and stake_amount < 0.0:
amount = abs(stake_amount) / current_rate
@@ -540,7 +545,8 @@ class Backtesting:
if remaining < min_stake:
# Remaining stake is too low to be sold.
return trade
pos_trade = self._exit_trade(trade, row, current_rate, amount)
exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT)
pos_trade = self._get_exit_for_signal(trade, row, exit_, amount)
if pos_trade is not None:
order = pos_trade.orders[-1]
if self._get_order_filled(order.price, row):
@@ -560,12 +566,7 @@ class Backtesting:
# Check if we need to adjust our current positions
if self.strategy.position_adjustment_enable:
check_adjust_entry = True
if self.strategy.max_entry_position_adjustment > -1:
entry_count = trade.nr_of_successful_entries
check_adjust_entry = (entry_count <= self.strategy.max_entry_position_adjustment)
if check_adjust_entry:
trade = self._get_adjust_trade_entry_for_candle(trade, row)
trade = self._get_adjust_trade_entry_for_candle(trade, row)
enter = row[SHORT_IDX] if trade.is_short else row[LONG_IDX]
exit_sig = row[ESHORT_IDX] if trade.is_short else row[ELONG_IDX]
@@ -580,14 +581,15 @@ class Backtesting:
return t
return None
def _get_exit_for_signal(self, trade: LocalTrade, row: Tuple,
exit_: ExitCheckTuple) -> Optional[LocalTrade]:
def _get_exit_for_signal(
self, trade: LocalTrade, row: Tuple, exit_: ExitCheckTuple,
amount: Optional[float] = None) -> Optional[LocalTrade]:
exit_candle_time: datetime = row[DATE_IDX].to_pydatetime()
if exit_.exit_flag:
trade.close_date = exit_candle_time
exit_reason = exit_.exit_reason
amount_ = amount if amount is not None else trade.amount
trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
try:
close_rate = self._get_close_rate(row, trade, exit_, trade_dur)
@@ -596,7 +598,8 @@ class Backtesting:
# call the custom exit price,with default value as previous close_rate
current_profit = trade.calc_profit_ratio(close_rate)
order_type = self.strategy.order_types['exit']
if exit_.exit_type in (ExitType.EXIT_SIGNAL, ExitType.CUSTOM_EXIT):
if exit_.exit_type in (ExitType.EXIT_SIGNAL, ExitType.CUSTOM_EXIT,
ExitType.PARTIAL_EXIT):
# Checks and adds an exit tag, after checking that the length of the
# row has the length for an exit tag column
if (
@@ -624,22 +627,23 @@ class Backtesting:
# Confirm trade exit:
time_in_force = self.strategy.order_time_in_force['exit']
if (exit_.exit_type != ExitType.LIQUIDATION and not strategy_safe_wrapper(
self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair,
trade=trade, # type: ignore[arg-type]
order_type=order_type,
amount=trade.amount,
rate=close_rate,
time_in_force=time_in_force,
sell_reason=exit_reason, # deprecated
exit_reason=exit_reason,
current_time=exit_candle_time)):
if (exit_.exit_type not in (ExitType.LIQUIDATION, ExitType.PARTIAL_EXIT)
and not strategy_safe_wrapper(
self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair,
trade=trade, # type: ignore[arg-type]
order_type=order_type,
amount=amount_,
rate=close_rate,
time_in_force=time_in_force,
sell_reason=exit_reason, # deprecated
exit_reason=exit_reason,
current_time=exit_candle_time)):
return None
trade.exit_reason = exit_reason
return self._exit_trade(trade, row, close_rate, trade.amount)
return self._exit_trade(trade, row, close_rate, amount_)
return None
def _exit_trade(self, trade: LocalTrade, sell_row: Tuple,
@@ -647,7 +651,10 @@ class Backtesting:
self.order_id_counter += 1
exit_candle_time = sell_row[DATE_IDX].to_pydatetime()
order_type = self.strategy.order_types['exit']
amount = amount or trade.amount
# amount = amount or trade.amount
amount = amount_to_contract_precision(amount or trade.amount, trade.amount_precision,
self.precision_mode, trade.contract_size)
rate = price_to_precision(close_rate, trade.price_precision, self.precision_mode)
order = Order(
id=self.order_id_counter,
ft_trade_id=trade.id,
@@ -661,12 +668,12 @@ class Backtesting:
side=trade.exit_side,
order_type=order_type,
status="open",
price=close_rate,
average=close_rate,
price=rate,
average=rate,
amount=amount,
filled=0,
remaining=amount,
cost=amount * close_rate,
cost=amount * rate,
)
trade.orders.append(order)
return trade
@@ -812,7 +819,17 @@ class Backtesting:
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
self.order_id_counter += 1
base_currency = self.exchange.get_pair_base_currency(pair)
amount = round((stake_amount / propose_rate) * leverage, 8)
precision_price = self.exchange.get_precision_price(pair)
propose_rate = price_to_precision(propose_rate, precision_price, self.precision_mode)
amount_p = (stake_amount / propose_rate) * leverage
contract_size = self.exchange.get_contract_size(pair)
precision_amount = self.exchange.get_precision_amount(pair)
amount = amount_to_contract_precision(amount_p, precision_amount, self.precision_mode,
contract_size)
# Backcalculate actual stake amount.
stake_amount = amount * propose_rate / leverage
is_short = (direction == 'short')
# Necessary for Margin trading. Disabled until support is enabled.
# interest_rate = self.exchange.get_interest_rate()
@@ -841,9 +858,10 @@ class Backtesting:
trading_mode=self.trading_mode,
leverage=leverage,
# interest_rate=interest_rate,
amount_precision=self.exchange.get_precision_amount(pair),
price_precision=self.exchange.get_precision_price(pair),
amount_precision=precision_amount,
price_precision=precision_price,
precision_mode=self.precision_mode,
contract_size=contract_size,
orders=[],
)
@@ -853,7 +871,8 @@ class Backtesting:
pair=pair,
open_rate=propose_rate,
amount=amount,
leverage=leverage,
stake_amount=trade.stake_amount,
wallet_balance=trade.stake_amount,
is_short=is_short,
))

View File

@@ -24,13 +24,15 @@ from pandas import DataFrame
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN
from freqtrade.data.converter import trim_dataframes
from freqtrade.data.history import get_timerange
from freqtrade.enums import HyperoptState
from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, file_dump_json, plural
from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer
from freqtrade.optimize.hyperopt_tools import (HyperoptStateContainer, HyperoptTools,
hyperopt_serializer)
from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
@@ -74,10 +76,14 @@ class Hyperopt:
self.dimensions: List[Dimension] = []
self.config = config
self.min_date: datetime
self.max_date: datetime
self.backtesting = Backtesting(self.config)
self.pairlist = self.backtesting.pairlists.whitelist
self.custom_hyperopt: HyperOptAuto
self.analyze_per_epoch = self.config.get('analyze_per_epoch', False)
HyperoptStateContainer.set_state(HyperoptState.STARTUP)
if not self.config.get('hyperopt'):
self.custom_hyperopt = HyperOptAuto(self.config)
@@ -290,6 +296,7 @@ class Hyperopt:
Called once per epoch to optimize whatever is configured.
Keep this function as optimized as possible!
"""
HyperoptStateContainer.set_state(HyperoptState.OPTIMIZE)
backtest_start_time = datetime.now(timezone.utc)
params_dict = self._get_params_dict(self.dimensions, raw_params)
@@ -321,6 +328,10 @@ class Hyperopt:
with self.data_pickle_file.open('rb') as f:
processed = load(f, mmap_mode='r')
if self.analyze_per_epoch:
# Data is not yet analyzed, rerun populate_indicators.
processed = self.advise_and_trim(processed)
bt_results = self.backtesting.backtest(
processed=processed,
start_date=self.min_date,
@@ -406,22 +417,33 @@ class Hyperopt:
def _set_random_state(self, random_state: Optional[int]) -> int:
return random_state or random.randint(1, 2**16 - 1)
def prepare_hyperopt_data(self) -> None:
data, timerange = self.backtesting.load_bt_data()
self.backtesting.load_bt_data_detail()
logger.info("Dataload complete. Calculating indicators")
def advise_and_trim(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
preprocessed = self.backtesting.strategy.advise_all_indicators(data)
# Trim startup period from analyzed dataframe to get correct dates for output.
processed = trim_dataframes(preprocessed, timerange, self.backtesting.required_startup)
processed = trim_dataframes(preprocessed, self.timerange, self.backtesting.required_startup)
self.min_date, self.max_date = get_timerange(processed)
return processed
logger.info(f'Hyperopting with data from {self.min_date.strftime(DATETIME_PRINT_FORMAT)} '
f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} '
f'({(self.max_date - self.min_date).days} days)..')
# Store non-trimmed data - will be trimmed after signal generation.
dump(preprocessed, self.data_pickle_file)
def prepare_hyperopt_data(self) -> None:
HyperoptStateContainer.set_state(HyperoptState.DATALOAD)
data, self.timerange = self.backtesting.load_bt_data()
self.backtesting.load_bt_data_detail()
logger.info("Dataload complete. Calculating indicators")
if not self.analyze_per_epoch:
HyperoptStateContainer.set_state(HyperoptState.INDICATORS)
preprocessed = self.advise_and_trim(data)
logger.info(f'Hyperopting with data from '
f'{self.min_date.strftime(DATETIME_PRINT_FORMAT)} '
f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} '
f'({(self.max_date - self.min_date).days} days)..')
# Store non-trimmed data - will be trimmed after signal generation.
dump(preprocessed, self.data_pickle_file)
else:
dump(data, self.data_pickle_file)
def get_asked_points(self, n_points: int) -> Tuple[List[List[Any]], List[bool]]:
"""

View File

@@ -13,6 +13,7 @@ from colorama import Fore, Style
from pandas import isna, json_normalize
from freqtrade.constants import FTHYPT_FILEVERSION, USERPATH_STRATEGIES
from freqtrade.enums import HyperoptState
from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, round_coin_value, round_dict, safe_value_fallback2
from freqtrade.optimize.hyperopt_epoch_filters import hyperopt_filter_epochs
@@ -32,6 +33,15 @@ def hyperopt_serializer(x):
return str(x)
class HyperoptStateContainer():
""" Singleton class to track state of hyperopt"""
state: HyperoptState = HyperoptState.OPTIMIZE
@classmethod
def set_state(cls, value: HyperoptState):
cls.state = value
class HyperoptTools():
@staticmethod