Merge branch 'develop' into move_datadownload
This commit is contained in:
@@ -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,
|
||||
))
|
||||
|
||||
|
@@ -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]]:
|
||||
"""
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user