diff --git a/docs/exchanges.md b/docs/exchanges.md index b808096d2..18a7af5a1 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -64,7 +64,10 @@ Binance supports [time_in_force](configuration.md#understand-order_time_in_force For Binance, please add `"BNB/"` to your blacklist to avoid issues. Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB trade unsellable as the expected amount is not there anymore. -### Binance Futures' order pricing +### Binance Futures + +Binance has specific (unfortunately complex) [Futures Trading Quantitative Rules](https://www.binance.com/en/support/faq/4f462ebe6ff445d4a170be7d9e897272) which need to be followed, and which prohibit a too low stake-amount (among others) for too many orders. +Violating these rules will result in a trading restriction. When trading on Binance Futures market, orderbook must be used because there is no price ticker data for futures. diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 94b1230b3..5f3e46be9 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -419,7 +419,7 @@ The function must return either `True` (cancel order) or `False` (keep order ali ``` python from datetime import datetime, timedelta -from freqtrade.persistence import Trade +from freqtrade.persistence import Trade, Order class AwesomeStrategy(IStrategy): @@ -431,7 +431,7 @@ class AwesomeStrategy(IStrategy): 'exit': 60 * 25 } - def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, + def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -442,7 +442,7 @@ class AwesomeStrategy(IStrategy): return False - def check_exit_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: Trade, order: 'Order', current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -460,7 +460,7 @@ class AwesomeStrategy(IStrategy): ``` python from datetime import datetime -from freqtrade.persistence import Trade +from freqtrade.persistence import Trade, Order class AwesomeStrategy(IStrategy): @@ -472,22 +472,22 @@ class AwesomeStrategy(IStrategy): 'exit': 60 * 25 } - def check_entry_timeout(self, pair: str, trade: Trade, order: dict, + def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) current_price = ob['bids'][0][0] # Cancel buy order if price is more than 2% above the order. - if current_price > order['price'] * 1.02: + if current_price > order.price * 1.02: return True return False - def check_exit_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) current_price = ob['asks'][0][0] # Cancel sell order if price is more than 2% below the order. - if current_price < order['price'] * 0.98: + if current_price < order.price * 0.98: return True return False ``` diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index ac8d3e489..458e80d0e 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -183,11 +183,11 @@ class AwesomeStrategy(IStrategy): ``` python hl_lines="2 6" class AwesomeStrategy(IStrategy): - def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, + def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: return False - def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, + def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: return False ``` diff --git a/docs/updating.md b/docs/updating.md index b23ce32dc..1839edc4c 100644 --- a/docs/updating.md +++ b/docs/updating.md @@ -2,6 +2,10 @@ To update your freqtrade installation, please use one of the below methods, corresponding to your installation method. +!!! Note "Tracking changes" + Breaking changes / changed behavior will be documented in the changelog that is posted alongside every release. + For the develop branch, please follow PR's to avoid being surprised by changes. + ## docker-compose !!! Note "Legacy installations using the `master` image" diff --git a/freqtrade/configuration/PeriodicCache.py b/freqtrade/configuration/PeriodicCache.py index 64fff668e..1a535440d 100644 --- a/freqtrade/configuration/PeriodicCache.py +++ b/freqtrade/configuration/PeriodicCache.py @@ -16,4 +16,4 @@ class PeriodicCache(TTLCache): return ts - offset # Init with smlight offset - super().__init__(maxsize=maxsize, ttl=ttl-1e-5, timer=local_timer, getsizeof=getsizeof) + super().__init__(maxsize=maxsize, ttl=ttl - 1e-5, timer=local_timer, getsizeof=getsizeof) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 4ef531c7a..3fcbd1f2f 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -31,7 +31,7 @@ def log_config_error_range(path: str, errmsg: str) -> str: offset = int(offsetlist[0]) text = Path(path).read_text() # Fetch an offset of 80 characters around the error line - subtext = text[offset-min(80, offset):offset+80] + subtext = text[offset - min(80, offset):offset + 80] segments = subtext.split('\n') if len(segments) > 3: # Remove first and last lines, to avoid odd truncations diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index db7ef66fc..8abcc6747 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -149,7 +149,14 @@ def load_backtest_stats(filename: Union[Path, str]) -> Dict[str, Any]: return data -def _load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]): +def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]): + """ + Load one strategy from multi-strategy result + and merge it with results + :param strategy_name: Name of the strategy contained in the result + :param filename: Backtest-result-filename to load + :param results: dict to merge the result to. + """ bt_data = load_backtest_stats(filename) for k in ('metadata', 'strategy'): results[k][strategy_name] = bt_data[k][strategy_name] @@ -160,6 +167,30 @@ def _load_and_merge_backtest_result(strategy_name: str, filename: Path, results: break +def _get_backtest_files(dirname: Path) -> List[Path]: + return list(reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json')))) + + +def get_backtest_resultlist(dirname: Path): + """ + Get list of backtest results read from metadata files + """ + results = [] + for filename in _get_backtest_files(dirname): + metadata = load_backtest_metadata(filename) + if not metadata: + continue + for s, v in metadata.items(): + results.append({ + 'filename': filename.name, + 'strategy': s, + 'run_id': v['run_id'], + 'backtest_start_time': v['backtest_start_time'], + + }) + return results + + def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str], min_backtest_date: datetime = None) -> Dict[str, Any]: """ @@ -179,7 +210,7 @@ def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, s } # Weird glob expression here avoids including .meta.json files. - for filename in reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json'))): + for filename in _get_backtest_files(dirname): metadata = load_backtest_metadata(filename) if not metadata: # Files are sorted from newest to oldest. When file without metadata is encountered it @@ -202,7 +233,7 @@ def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, s if strategy_metadata['run_id'] == run_id: del run_ids[strategy_name] - _load_and_merge_backtest_result(strategy_name, filename, results) + load_and_merge_backtest_result(strategy_name, filename, results) if len(run_ids) == 0: break diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 82505759a..e0da0878d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -651,7 +651,7 @@ class Exchange: Re-implementation of ccxt internal methods - ensuring we can test the result is correct based on our definitions. """ - if self.markets[pair]['precision']['amount']: + if self.markets[pair]['precision']['amount'] is not None: amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, precision=self.markets[pair]['precision']['amount'], counting_mode=self.precisionMode, @@ -2177,7 +2177,7 @@ class Exchange: lev = tier['lev'] if tier_index < len(pair_tiers) - 1: - next_tier = pair_tiers[tier_index+1] + next_tier = pair_tiers[tier_index + 1] next_floor = next_tier['min'] / next_tier['lev'] if next_floor > stake_amount: # Next tier min too high for stake amount return min((tier['max'] / stake_amount), lev) diff --git a/freqtrade/leverage/interest.py b/freqtrade/leverage/interest.py index ff375b05e..367df5821 100644 --- a/freqtrade/leverage/interest.py +++ b/freqtrade/leverage/interest.py @@ -31,13 +31,13 @@ def interest( """ exchange_name = exchange_name.lower() if exchange_name == "binance": - return borrowed * rate * ceil(hours)/twenty_four + return borrowed * rate * ceil(hours) / twenty_four elif exchange_name == "kraken": # Rounded based on https://kraken-fees-calculator.github.io/ - return borrowed * rate * (one+ceil(hours/four)) + return borrowed * rate * (one + ceil(hours / four)) elif exchange_name == "ftx": # As Explained under #Interest rates section in # https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer - return borrowed * rate * ceil(hours)/twenty_four + return borrowed * rate * ceil(hours) / twenty_four else: raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade") diff --git a/freqtrade/misc.py b/freqtrade/misc.py index acc7fc2e4..d5572ea0b 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -126,7 +126,7 @@ def format_ms_time(date: int) -> str: convert MS date to readable format. : epoch-string in ms """ - return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S') + return datetime.fromtimestamp(date / 1000.0).strftime('%Y-%m-%dT%H:%M:%S') def deep_merge_dicts(source, destination, allow_null_overrides: bool = True): diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 8c84f772a..8d3c3a266 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -390,8 +390,8 @@ class HyperoptTools(): lambda x: '{} {}'.format( round_coin_value(x['Total profit'], stake_currency, keep_trailing_zeros=True), f"({x['Profit']:,.2%})".rjust(10, ' ') - ).rjust(25+len(stake_currency)) - if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)), + ).rjust(25 + len(stake_currency)) + if x['Total profit'] != 0.0 else '--'.rjust(25 + len(stake_currency)), axis=1 ) trials = trials.drop(columns=['Total profit']) @@ -399,11 +399,11 @@ class HyperoptTools(): if print_colorized: for i in range(len(trials)): if trials.loc[i]['is_profit']: - for j in range(len(trials.loc[i])-3): + for j in range(len(trials.loc[i]) - 3): trials.iat[i, j] = "{}{}{}".format(Fore.GREEN, str(trials.loc[i][j]), Fore.RESET) if trials.loc[i]['is_best'] and highlight_best: - for j in range(len(trials.loc[i])-3): + for j in range(len(trials.loc[i]) - 3): trials.iat[i, j] = "{}{}{}".format(Style.BRIGHT, str(trials.loc[i][j]), Style.RESET_ALL) @@ -459,7 +459,7 @@ class HyperoptTools(): 'loss', 'is_initial_point', 'is_best'] perc_multi = 100 - param_metrics = [("params_dict."+param) for param in results[0]['params_dict'].keys()] + param_metrics = [("params_dict." + param) for param in results[0]['params_dict'].keys()] trials = trials[base_metrics + param_metrics] base_columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Median profit', 'Total profit', diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 05de39caf..a9c07f12c 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -360,7 +360,7 @@ class LocalTrade(): if self.has_no_leverage: return 0.0 elif not self.is_short: - return (self.amount * self.open_rate) * ((self.leverage-1)/self.leverage) + return (self.amount * self.open_rate) * ((self.leverage - 1) / self.leverage) else: return self.amount @@ -747,7 +747,7 @@ class LocalTrade(): now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None) sec_per_hour = Decimal(3600) total_seconds = Decimal((now - open_date).total_seconds()) - hours = total_seconds/sec_per_hour or zero + hours = total_seconds / sec_per_hour or zero rate = Decimal(interest_rate or self.interest_rate) borrowed = Decimal(self.borrowed) @@ -861,9 +861,9 @@ class LocalTrade(): return 0.0 else: if self.is_short: - profit_ratio = (1 - (close_trade_value/self.open_trade_value)) * leverage + profit_ratio = (1 - (close_trade_value / self.open_trade_value)) * leverage else: - profit_ratio = ((close_trade_value/self.open_trade_value) - 1) * leverage + profit_ratio = ((close_trade_value / self.open_trade_value) - 1) * leverage return float(f"{profit_ratio:.8f}") diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index 7a355c291..6aa857c2c 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -107,7 +107,7 @@ class VolatilityFilter(IPairList): returns = (np.log(daily_candles.close / daily_candles.close.shift(-1))) returns.fillna(0, inplace=True) - volatility_series = returns.rolling(window=self._days).std()*np.sqrt(self._days) + volatility_series = returns.rolling(window=self._days).std() * np.sqrt(self._days) volatility_avg = volatility_series.mean() if self._min_volatility <= volatility_avg <= self._max_volatility: diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 757ed8aac..a902ea984 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -1,13 +1,16 @@ import asyncio import logging from copy import deepcopy +from typing import Any, Dict, List from fastapi import APIRouter, BackgroundTasks, Depends from freqtrade.configuration.config_validation import validate_config_consistency +from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result from freqtrade.enums import BacktestState from freqtrade.exceptions import DependencyException -from freqtrade.rpc.api_server.api_schemas import BacktestRequest, BacktestResponse +from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, + BacktestResponse) from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode from freqtrade.rpc.api_server.webserver import ApiServer from freqtrade.rpc.rpc import RPCException @@ -200,3 +203,30 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest ended", } + + +@router.get('/backtest/history', response_model=List[BacktestHistoryEntry], tags=['webserver', 'backtest']) +def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): + # Get backtest result history, read from metadata files + return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results') + + +@router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest']) +def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): + # Get backtest result history, read from metadata files + fn = config['user_data_dir'] / 'backtest_results' / filename + results: Dict[str, Any] = { + 'metadata': {}, + 'strategy': {}, + 'strategy_comparison': [], + } + + load_and_merge_backtest_result(strategy, fn, results) + return { + "status": "ended", + "running": False, + "step": "", + "progress": 1, + "status_msg": "Historic result", + "backtest_result": results, + } diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index ae797edad..a9135cce2 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -421,6 +421,13 @@ class BacktestResponse(BaseModel): backtest_result: Optional[Dict[str, Any]] +class BacktestHistoryEntry(BaseModel): + filename: str + strategy: str + run_id: str + backtest_start_time: int + + class SysInfo(BaseModel): cpu_pct: List[float] ram_pct: float diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index d96154824..5021c99f9 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -35,7 +35,8 @@ logger = logging.getLogger(__name__) # 1.13: forcebuy supports stake_amount # versions 2.xx -> futures/short branch # 2.14: Add entry/exit orders to trade response -API_VERSION = 2.14 +# 2.15: Add backtest history endpoints +API_VERSION = 2.15 # Public API, requires no auth. router_public = APIRouter() diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index b152b722c..c2531fec3 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -387,7 +387,7 @@ class Telegram(RPCHandler): else: return "\N{CROSS MARK}" - def _prepare_entry_details(self, filled_orders: List, base_currency: str, is_open: bool): + def _prepare_entry_details(self, filled_orders: List, quote_currency: str, is_open: bool): """ Prepare details of trade with entry adjustment enabled """ @@ -405,7 +405,7 @@ class Telegram(RPCHandler): if x == 0: lines.append(f"*Entry #{x+1}:*") lines.append( - f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {base_currency})") + f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") lines.append(f"*Average Entry Price:* {cur_entry_average}") else: sumA = 0 @@ -419,7 +419,8 @@ class Telegram(RPCHandler): if prev_avg_price: minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price - dur_entry = cur_entry_datetime - arrow.get(filled_orders[x-1]["order_filled_date"]) + dur_entry = cur_entry_datetime - arrow.get( + filled_orders[x - 1]["order_filled_date"]) days = dur_entry.days hours, remainder = divmod(dur_entry.seconds, 3600) minutes, seconds = divmod(remainder, 60) @@ -428,7 +429,7 @@ class Telegram(RPCHandler): lines.append("({})".format(cur_entry_datetime .humanize(granularity=["day", "hour", "minute"]))) lines.append( - f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {base_currency})") + f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") lines.append(f"*Average Entry Price:* {cur_entry_average} " f"({price_to_1st_entry:.2%} from 1st entry rate)") lines.append(f"*Order filled at:* {order['order_filled_date']}") @@ -471,7 +472,7 @@ class Telegram(RPCHandler): "*Current Pair:* {pair}", "*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"), "*Leverage:* `{leverage}`" if r.get('leverage') else "", - "*Amount:* `{amount} ({stake_amount} {base_currency})`", + "*Amount:* `{amount} ({stake_amount} {quote_currency})`", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "", ] diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index aeda66d4e..57fd07042 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -206,18 +206,18 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass - def check_buy_timeout(self, pair: str, trade: Trade, order: dict, + def check_buy_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ DEPRECATED: Please use `check_entry_timeout` instead. """ return False - def check_entry_timeout(self, pair: str, trade: Trade, order: dict, + def check_entry_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ Check entry timeout function callback. - This method can be used to override the enter-timeout. + This method can be used to override the entry-timeout. It is called whenever a limit entry order has been created, and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, @@ -225,8 +225,8 @@ class IStrategy(ABC, HyperStrategyMixin): When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for - :param trade: trade object. - :param order: Order dictionary as returned from CCXT. + :param trade: Trade object. + :param order: Order object. :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the entry order is cancelled. @@ -234,30 +234,30 @@ class IStrategy(ABC, HyperStrategyMixin): return self.check_buy_timeout( pair=pair, trade=trade, order=order, current_time=current_time) - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + def check_sell_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ DEPRECATED: Please use `check_exit_timeout` instead. """ return False - def check_exit_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ - Check sell timeout function callback. + Check exit timeout function callback. This method can be used to override the exit-timeout. - It is called whenever a (long) limit sell order or (short) limit buy - has been created, and is not yet fully filled. + It is called whenever a limit exit order has been created, + and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for - :param trade: trade object. - :param order: Order dictionary as returned from CCXT. + :param trade: Trade object. + :param order: Order object :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the (long)sell/(short)buy-order is cancelled. + :return bool: When True is returned, then the exit-order is cancelled. """ return self.check_sell_timeout( pair=pair, trade=trade, order=order, current_time=current_time) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index f07c14e24..a36cb3dbb 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -93,9 +93,9 @@ def stoploss_from_open( return 1 if is_short is True: - stoploss = -1+((1-open_relative_stop)/(1-current_profit)) + stoploss = -1 + ((1 - open_relative_stop) / (1 - current_profit)) else: - stoploss = 1-((1+open_relative_stop)/(1+current_profit)) + stoploss = 1 - ((1 + open_relative_stop) / (1 + current_profit)) # negative stoploss values indicate the requested stop price is higher/lower # (long/short) than the current price diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index c7e69d3e4..90dbade91 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -191,7 +191,8 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: """ return True -def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: +def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', + current_time: datetime, **kwargs) -> bool: """ Check entry timeout function callback. This method can be used to override the entry-timeout. @@ -204,14 +205,16 @@ def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for - :param trade: trade object. - :param order: Order dictionary as returned from CCXT. + :param trade: Trade object. + :param order: Order object. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the buy-order is cancelled. + :return bool: When True is returned, then the entry order is cancelled. """ return False -def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: +def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', + current_time: datetime, **kwargs) -> bool: """ Check exit timeout function callback. This method can be used to override the exit-timeout. @@ -224,8 +227,9 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) - When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for - :param trade: trade object. - :param order: Order dictionary as returned from CCXT. + :param trade: Trade object. + :param order: Order object. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the exit-order is cancelled. """ diff --git a/setup.cfg b/setup.cfg index 6aaec9d73..f4a90bda7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,9 @@ console_scripts = freqtrade = freqtrade.main:main [flake8] -#ignore = +# Default from https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-ignore +# minus E226 +ignore = E121,E123,E126,E24,E704,W503,W504 max-line-length = 100 max-complexity = 12 exclude = diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 22869638b..1431bd22a 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1429,7 +1429,7 @@ def test_backtesting_show(mocker, testdatadir, capsys): args = [ "backtesting-show", "--export-filename", - f"{testdatadir / 'backtest-result_new.json'}", + f"{testdatadir / 'backtest_results/backtest-result_new.json'}", "--show-pair-list" ] pargs = get_args(args) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index f4275edd9..2b53e4900 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -27,18 +27,19 @@ def test_get_latest_backtest_filename(testdatadir, mocker): with pytest.raises(ValueError, match=r"Directory .* does not seem to contain .*"): - get_latest_backtest_filename(testdatadir.parent) + get_latest_backtest_filename(testdatadir) - res = get_latest_backtest_filename(testdatadir) + testdir_bt = testdatadir / "backtest_results" + res = get_latest_backtest_filename(testdir_bt) assert res == 'backtest-result_new.json' - res = get_latest_backtest_filename(str(testdatadir)) + res = get_latest_backtest_filename(str(testdir_bt)) assert res == 'backtest-result_new.json' mocker.patch("freqtrade.data.btanalysis.json_load", return_value={}) with pytest.raises(ValueError, match=r"Invalid '.last_result.json' format."): - get_latest_backtest_filename(testdatadir) + get_latest_backtest_filename(testdir_bt) def test_get_latest_hyperopt_file(testdatadir): @@ -81,7 +82,7 @@ def test_load_backtest_data_old_format(testdatadir, mocker): def test_load_backtest_data_new_format(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) assert isinstance(bt_data, DataFrame) assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp']) @@ -92,19 +93,19 @@ def test_load_backtest_data_new_format(testdatadir): assert bt_data.equals(bt_data2) # Test loading from folder (must yield same result) - bt_data3 = load_backtest_data(testdatadir) + bt_data3 = load_backtest_data(testdatadir / "backtest_results") assert bt_data.equals(bt_data3) with pytest.raises(ValueError, match=r"File .* does not exist\."): load_backtest_data(str("filename") + "nofile") with pytest.raises(ValueError, match=r"Unknown dataformat."): - load_backtest_data(testdatadir / LAST_BT_RESULT_FN) + load_backtest_data(testdatadir / "backtest_results" / LAST_BT_RESULT_FN) def test_load_backtest_data_multi(testdatadir): - filename = testdatadir / "backtest-result_multistrat.json" + filename = testdatadir / "backtest_results/backtest-result_multistrat.json" for strategy in ('StrategyTestV2', 'TestStrategy'): bt_data = load_backtest_data(filename, strategy=strategy) assert isinstance(bt_data, DataFrame) @@ -182,7 +183,7 @@ def test_extract_trades_of_period(testdatadir): def test_analyze_trade_parallelism(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) res = analyze_trade_parallelism(bt_data, "5m") @@ -256,7 +257,7 @@ def test_combine_dataframes_with_mean_no_data(testdatadir): def test_create_cum_profit(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") @@ -272,7 +273,7 @@ def test_create_cum_profit(testdatadir): def test_create_cum_profit1(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) # Move close-time to "off" the candle, to make sure the logic still works bt_data.loc[:, 'close_date'] = bt_data.loc[:, 'close_date'] + DateOffset(seconds=20) @@ -294,7 +295,7 @@ def test_create_cum_profit1(testdatadir): def test_calculate_max_drawdown(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) _, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown( bt_data, value_col="profit_abs") @@ -318,7 +319,7 @@ def test_calculate_max_drawdown(testdatadir): def test_calculate_csum(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) csum_min, csum_max = calculate_csum(bt_data) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index f0417c4c5..e99121489 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -231,6 +231,10 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): (2.34559, 2, 3, 1, 2.345, 'spot'), (2.9999, 2, 3, 1, 2.999, 'spot'), (2.9909, 2, 3, 1, 2.990, 'spot'), + (2.9909, 2, 0, 1, 2, 'spot'), + (29991.5555, 2, 0, 1, 29991, 'spot'), + (29991.5555, 2, -1, 1, 29990, 'spot'), + (29991.5555, 2, -2, 1, 29900, 'spot'), # Tests for Tick-size (2.34559, 4, 0.0001, 1, 2.3455, 'spot'), (2.34559, 4, 0.00001, 1, 2.34559, 'spot'), @@ -382,11 +386,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: ) # min result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - expected_result = 2 * (1+0.05) / (1-abs(stoploss)) + expected_result = 2 * (1 + 0.05) / (1 - abs(stoploss)) assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) - assert isclose(result, expected_result/3) + assert isclose(result, expected_result / 3) # max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 10000 @@ -401,11 +405,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - expected_result = 2 * 2 * (1+0.05) / (1-abs(stoploss)) + expected_result = 2 * 2 * (1 + 0.05) / (1 - abs(stoploss)) assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) - assert isclose(result, expected_result/5) + assert isclose(result, expected_result / 5) # max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 20000 @@ -420,11 +424,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - expected_result = max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss)) + expected_result = max(2, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10) - assert isclose(result, expected_result/10) + assert isclose(result, expected_result / 10) # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { @@ -436,11 +440,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - expected_result = max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss)) + expected_result = max(8, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) - assert isclose(result, expected_result/7.0) + assert isclose(result, expected_result / 7.0) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 @@ -450,7 +454,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) - assert isclose(result, expected_result/8.0) + assert isclose(result, expected_result / 8.0) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 @@ -461,7 +465,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) - assert isclose(result, expected_result/12) + assert isclose(result, expected_result / 12) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 @@ -489,7 +493,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: ) # With Leverage, Contract size 10 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) - assert isclose(result, (expected_result/12) * 10.0) + assert isclose(result, (expected_result / 12) * 10.0) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 10000 @@ -510,7 +514,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss) - expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)) + expected_result = max(0.0001, 0.001 * 0.020405) * (1 + 0.05) / (1 - abs(stoploss)) assert round(result, 8) == round(expected_result, 8) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2.0) @@ -518,12 +522,12 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: # Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) - assert round(result, 8) == round(expected_result/3, 8) + assert round(result, 8) == round(expected_result / 3, 8) # Contract_size markets["ETH/BTC"]["contractSize"] = 0.1 result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) - assert round(result, 8) == round((expected_result/3), 8) + assert round(result, 8) == round((expected_result / 3), 8) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 12.0) @@ -2691,9 +2695,10 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha # Monkey-patch async function exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist) pair = 'ETH/BTC' - ret = await exchange._async_get_trade_history_time(pair, - since=fetch_trades_result[0]['timestamp'], - until=fetch_trades_result[-1]['timestamp']-1) + ret = await exchange._async_get_trade_history_time( + pair, + since=fetch_trades_result[0]['timestamp'], + until=fetch_trades_result[-1]['timestamp'] - 1) assert type(ret) is tuple assert ret[0] == pair assert type(ret[1]) is list @@ -2729,7 +2734,7 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist) pair = 'ETH/BTC' ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0][0], - until=trades_history[-1][0]-1) + until=trades_history[-1][0] - 1) assert type(ret) is tuple assert ret[0] == pair assert type(ret[1]) is list diff --git a/tests/leverage/test_interest.py b/tests/leverage/test_interest.py index c7e787bdb..6b189ce50 100644 --- a/tests/leverage/test_interest.py +++ b/tests/leverage/test_interest.py @@ -6,7 +6,7 @@ import pytest from freqtrade.leverage import interest -ten_mins = Decimal(1/6) +ten_mins = Decimal(1 / 6) five_hours = Decimal(5.0) twentyfive_hours = Decimal(25.0) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index ad9bcd978..05c0bf575 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -190,7 +190,7 @@ def test_store_backtest_stats(testdatadir, mocker): assert dump_mock.call_count == 3 assert isinstance(dump_mock.call_args_list[0][0][0], Path) - assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir/'backtest-result')) + assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'backtest-result')) dump_mock.reset_mock() filename = testdatadir / 'testresult.json' @@ -228,7 +228,7 @@ def test_generate_pair_metrics(): def test_generate_daily_stats(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) res = generate_daily_stats(bt_data) assert isinstance(res, dict) @@ -248,7 +248,7 @@ def test_generate_daily_stats(testdatadir): def test_generate_trading_stats(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) res = generate_trading_stats(bt_data) assert isinstance(res, dict) @@ -332,7 +332,7 @@ def test_generate_sell_reason_stats(): def test_text_table_strategy(testdatadir): - filename = testdatadir / "backtest-result_multistrat.json" + filename = testdatadir / "backtest_results/backtest-result_multistrat.json" bt_res_data = load_backtest_stats(filename) bt_res_data_comparison = bt_res_data.pop('strategy_comparison') @@ -364,7 +364,7 @@ def test_generate_edge_table(): def test_generate_periodic_breakdown_stats(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename).to_dict(orient='records') res = generate_periodic_breakdown_stats(bt_data, 'day') @@ -392,7 +392,7 @@ def test__get_resample_from_period(): def test_show_sorted_pairlist(testdatadir, default_conf, capsys): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_stats(filename) default_conf['backtest_show_pair_list'] = True diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 54bf07dc2..af8361571 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1581,6 +1581,38 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): assert result['status_msg'] == 'Backtest reset' +def test_api_backtest_history(botclient, mocker, testdatadir): + ftbot, client = botclient + mocker.patch('freqtrade.data.btanalysis._get_backtest_files', + return_value=[ + testdatadir / 'backtest_results/backtest-result_multistrat.json', + testdatadir / 'backtest_results/backtest-result_new.json' + ]) + + rc = client_get(client, f"{BASE_URI}/backtest/history") + assert_response(rc, 502) + ftbot.config['user_data_dir'] = testdatadir + ftbot.config['runmode'] = RunMode.WEBSERVER + + rc = client_get(client, f"{BASE_URI}/backtest/history") + assert_response(rc) + result = rc.json() + assert len(result) == 3 + fn = result[0]['filename'] + assert fn == "backtest-result_multistrat.json" + strategy = result[0]['strategy'] + rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}&strategy={strategy}") + assert_response(rc) + result2 = rc.json() + assert result2 + assert result2['status'] == 'ended' + assert not result2['running'] + assert result2['progress'] == 1 + # Only one strategy loaded - even though we use multiresult + assert len(result2['backtest_result']['strategy']) == 1 + assert result2['backtest_result']['strategy'][strategy] + + def test_health(botclient): ftbot, client = botclient diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 205fb4dac..65fb9f6dc 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -164,7 +164,7 @@ def test_stoploss_from_absolute(): assert pytest.approx(stoploss_from_absolute(90, 100, True)) == 0 assert pytest.approx(stoploss_from_absolute(100, 100, True)) == 0 - assert pytest.approx(stoploss_from_absolute(110, 100, True)) == -(1 - (110/100)) + assert pytest.approx(stoploss_from_absolute(110, 100, True)) == -(1 - (110 / 100)) assert pytest.approx(stoploss_from_absolute(110, 100, True)) == 0.1 assert pytest.approx(stoploss_from_absolute(105, 100, True)) == 0.05 assert pytest.approx(stoploss_from_absolute(100, 0, True)) == 1 diff --git a/tests/test_persistence.py b/tests/test_persistence.py index ecac561f8..801e0e35f 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -119,7 +119,7 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.stop_loss is None assert trade.initial_stop_loss is None - trade._set_stop_loss(0.1, (1.0/9.0)) + trade._set_stop_loss(0.1, (1.0 / 9.0)) assert trade.liquidation_price == 0.09 assert trade.stop_loss == 0.1 assert trade.initial_stop_loss == 0.1 @@ -160,7 +160,7 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.stop_loss is None assert trade.initial_stop_loss is None - trade._set_stop_loss(0.08, (1.0/9.0)) + trade._set_stop_loss(0.08, (1.0 / 9.0)) assert trade.liquidation_price == 0.09 assert trade.stop_loss == 0.08 assert trade.initial_stop_loss == 0.08 @@ -171,13 +171,13 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.initial_stop_loss == 0.08 trade.set_isolated_liq(0.07) - trade._set_stop_loss(0.1, (1.0/8.0)) + trade._set_stop_loss(0.1, (1.0 / 8.0)) assert trade.liquidation_price == 0.07 assert trade.stop_loss == 0.07 assert trade.initial_stop_loss == 0.08 # Stop doesn't move stop higher - trade._set_stop_loss(0.1, (1.0/9.0)) + trade._set_stop_loss(0.1, (1.0 / 9.0)) assert trade.liquidation_price == 0.07 assert trade.stop_loss == 0.07 assert trade.initial_stop_loss == 0.08 diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 940639465..97f367608 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -157,7 +157,7 @@ def test_plot_trades(testdatadir, caplog): assert fig == fig1 assert log_has("No trades found.", caplog) pair = "ADA/BTC" - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" trades = load_backtest_data(filename) trades = trades.loc[trades['pair'] == pair] @@ -298,7 +298,7 @@ def test_generate_plot_file(mocker, caplog): def test_add_profit(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") @@ -318,7 +318,7 @@ def test_add_profit(testdatadir): def test_generate_profit_graph(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" trades = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") pairs = ["TRX/BTC", "XLM/BTC"] @@ -456,7 +456,7 @@ def test_plot_profit(default_conf, mocker, testdatadir): match=r"No trades found, cannot generate Profit-plot.*"): plot_profit(default_conf) - default_conf['exportfilename'] = testdatadir / "backtest-result_new.json" + default_conf['exportfilename'] = testdatadir / "backtest_results/backtest-result_new.json" plot_profit(default_conf) diff --git a/tests/testdata/.last_result.json b/tests/testdata/backtest_results/.last_result.json similarity index 100% rename from tests/testdata/.last_result.json rename to tests/testdata/backtest_results/.last_result.json diff --git a/tests/testdata/backtest-result_multistrat.json b/tests/testdata/backtest_results/backtest-result_multistrat.json similarity index 100% rename from tests/testdata/backtest-result_multistrat.json rename to tests/testdata/backtest_results/backtest-result_multistrat.json diff --git a/tests/testdata/backtest_results/backtest-result_multistrat.meta.json b/tests/testdata/backtest_results/backtest-result_multistrat.meta.json new file mode 100644 index 000000000..906edcece --- /dev/null +++ b/tests/testdata/backtest_results/backtest-result_multistrat.meta.json @@ -0,0 +1,10 @@ +{ + "StrategyTestV2": { + "run_id": "430d0271075ef327edbb23088f4db4ebe51a3dbf", + "backtest_start_time": 1648904006 + }, + "TestStrategy": { + "run_id": "110d0271075ef327edbb23085102b4ebe51a3d55", + "backtest_start_time": 1648904006 + } +} diff --git a/tests/testdata/backtest-result_new.json b/tests/testdata/backtest_results/backtest-result_new.json similarity index 100% rename from tests/testdata/backtest-result_new.json rename to tests/testdata/backtest_results/backtest-result_new.json diff --git a/tests/testdata/backtest_results/backtest-result_new.meta.json b/tests/testdata/backtest_results/backtest-result_new.meta.json new file mode 100644 index 000000000..57ecdb19d --- /dev/null +++ b/tests/testdata/backtest_results/backtest-result_new.meta.json @@ -0,0 +1,6 @@ +{ + "StrategyTestV3": { + "run_id": "430d0271075ef327edbb23088f4db4ebe51a3dbf", + "backtest_start_time": 1648904006 + } +}