Merge branch 'develop' into feat_readjust_entry
This commit is contained in:
commit
d65b64a46f
@ -64,7 +64,10 @@ Binance supports [time_in_force](configuration.md#understand-order_time_in_force
|
|||||||
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues.
|
For Binance, please add `"BNB/<STAKE>"` 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.
|
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.
|
When trading on Binance Futures market, orderbook must be used because there is no price ticker data for futures.
|
||||||
|
|
||||||
|
@ -419,7 +419,7 @@ The function must return either `True` (cancel order) or `False` (keep order ali
|
|||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade, Order
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@ -431,7 +431,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
'exit': 60 * 25
|
'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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
||||||
return True
|
return True
|
||||||
@ -442,7 +442,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
return False
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
||||||
return True
|
return True
|
||||||
@ -460,7 +460,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade, Order
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@ -472,22 +472,22 @@ class AwesomeStrategy(IStrategy):
|
|||||||
'exit': 60 * 25
|
'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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
ob = self.dp.orderbook(pair, 1)
|
ob = self.dp.orderbook(pair, 1)
|
||||||
current_price = ob['bids'][0][0]
|
current_price = ob['bids'][0][0]
|
||||||
# Cancel buy order if price is more than 2% above the order.
|
# 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 True
|
||||||
return False
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
ob = self.dp.orderbook(pair, 1)
|
ob = self.dp.orderbook(pair, 1)
|
||||||
current_price = ob['asks'][0][0]
|
current_price = ob['asks'][0][0]
|
||||||
# Cancel sell order if price is more than 2% below the order.
|
# 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 True
|
||||||
return False
|
return False
|
||||||
```
|
```
|
||||||
|
@ -183,11 +183,11 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
``` python hl_lines="2 6"
|
``` python hl_lines="2 6"
|
||||||
class AwesomeStrategy(IStrategy):
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
return False
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
return False
|
return False
|
||||||
```
|
```
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
To update your freqtrade installation, please use one of the below methods, corresponding to your installation method.
|
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
|
## docker-compose
|
||||||
|
|
||||||
!!! Note "Legacy installations using the `master` image"
|
!!! Note "Legacy installations using the `master` image"
|
||||||
|
@ -16,4 +16,4 @@ class PeriodicCache(TTLCache):
|
|||||||
return ts - offset
|
return ts - offset
|
||||||
|
|
||||||
# Init with smlight 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)
|
||||||
|
@ -31,7 +31,7 @@ def log_config_error_range(path: str, errmsg: str) -> str:
|
|||||||
offset = int(offsetlist[0])
|
offset = int(offsetlist[0])
|
||||||
text = Path(path).read_text()
|
text = Path(path).read_text()
|
||||||
# Fetch an offset of 80 characters around the error line
|
# 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')
|
segments = subtext.split('\n')
|
||||||
if len(segments) > 3:
|
if len(segments) > 3:
|
||||||
# Remove first and last lines, to avoid odd truncations
|
# Remove first and last lines, to avoid odd truncations
|
||||||
|
@ -149,7 +149,14 @@ def load_backtest_stats(filename: Union[Path, str]) -> Dict[str, Any]:
|
|||||||
return data
|
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)
|
bt_data = load_backtest_stats(filename)
|
||||||
for k in ('metadata', 'strategy'):
|
for k in ('metadata', 'strategy'):
|
||||||
results[k][strategy_name] = bt_data[k][strategy_name]
|
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
|
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],
|
def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str],
|
||||||
min_backtest_date: datetime = None) -> Dict[str, Any]:
|
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.
|
# 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)
|
metadata = load_backtest_metadata(filename)
|
||||||
if not metadata:
|
if not metadata:
|
||||||
# Files are sorted from newest to oldest. When file without metadata is encountered it
|
# 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:
|
if strategy_metadata['run_id'] == run_id:
|
||||||
del run_ids[strategy_name]
|
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:
|
if len(run_ids) == 0:
|
||||||
break
|
break
|
||||||
|
@ -651,7 +651,7 @@ class Exchange:
|
|||||||
Re-implementation of ccxt internal methods - ensuring we can test the result is correct
|
Re-implementation of ccxt internal methods - ensuring we can test the result is correct
|
||||||
based on our definitions.
|
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,
|
amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE,
|
||||||
precision=self.markets[pair]['precision']['amount'],
|
precision=self.markets[pair]['precision']['amount'],
|
||||||
counting_mode=self.precisionMode,
|
counting_mode=self.precisionMode,
|
||||||
@ -2177,7 +2177,7 @@ class Exchange:
|
|||||||
lev = tier['lev']
|
lev = tier['lev']
|
||||||
|
|
||||||
if tier_index < len(pair_tiers) - 1:
|
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']
|
next_floor = next_tier['min'] / next_tier['lev']
|
||||||
if next_floor > stake_amount: # Next tier min too high for stake amount
|
if next_floor > stake_amount: # Next tier min too high for stake amount
|
||||||
return min((tier['max'] / stake_amount), lev)
|
return min((tier['max'] / stake_amount), lev)
|
||||||
|
@ -31,13 +31,13 @@ def interest(
|
|||||||
"""
|
"""
|
||||||
exchange_name = exchange_name.lower()
|
exchange_name = exchange_name.lower()
|
||||||
if exchange_name == "binance":
|
if exchange_name == "binance":
|
||||||
return borrowed * rate * ceil(hours)/twenty_four
|
return borrowed * rate * ceil(hours) / twenty_four
|
||||||
elif exchange_name == "kraken":
|
elif exchange_name == "kraken":
|
||||||
# Rounded based on https://kraken-fees-calculator.github.io/
|
# 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":
|
elif exchange_name == "ftx":
|
||||||
# As Explained under #Interest rates section in
|
# As Explained under #Interest rates section in
|
||||||
# https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer
|
# 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:
|
else:
|
||||||
raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade")
|
raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade")
|
||||||
|
@ -126,7 +126,7 @@ def format_ms_time(date: int) -> str:
|
|||||||
convert MS date to readable format.
|
convert MS date to readable format.
|
||||||
: epoch-string in ms
|
: 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):
|
def deep_merge_dicts(source, destination, allow_null_overrides: bool = True):
|
||||||
|
@ -390,8 +390,8 @@ class HyperoptTools():
|
|||||||
lambda x: '{} {}'.format(
|
lambda x: '{} {}'.format(
|
||||||
round_coin_value(x['Total profit'], stake_currency, keep_trailing_zeros=True),
|
round_coin_value(x['Total profit'], stake_currency, keep_trailing_zeros=True),
|
||||||
f"({x['Profit']:,.2%})".rjust(10, ' ')
|
f"({x['Profit']:,.2%})".rjust(10, ' ')
|
||||||
).rjust(25+len(stake_currency))
|
).rjust(25 + len(stake_currency))
|
||||||
if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)),
|
if x['Total profit'] != 0.0 else '--'.rjust(25 + len(stake_currency)),
|
||||||
axis=1
|
axis=1
|
||||||
)
|
)
|
||||||
trials = trials.drop(columns=['Total profit'])
|
trials = trials.drop(columns=['Total profit'])
|
||||||
@ -399,11 +399,11 @@ class HyperoptTools():
|
|||||||
if print_colorized:
|
if print_colorized:
|
||||||
for i in range(len(trials)):
|
for i in range(len(trials)):
|
||||||
if trials.loc[i]['is_profit']:
|
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,
|
trials.iat[i, j] = "{}{}{}".format(Fore.GREEN,
|
||||||
str(trials.loc[i][j]), Fore.RESET)
|
str(trials.loc[i][j]), Fore.RESET)
|
||||||
if trials.loc[i]['is_best'] and highlight_best:
|
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,
|
trials.iat[i, j] = "{}{}{}".format(Style.BRIGHT,
|
||||||
str(trials.loc[i][j]), Style.RESET_ALL)
|
str(trials.loc[i][j]), Style.RESET_ALL)
|
||||||
|
|
||||||
@ -459,7 +459,7 @@ class HyperoptTools():
|
|||||||
'loss', 'is_initial_point', 'is_best']
|
'loss', 'is_initial_point', 'is_best']
|
||||||
perc_multi = 100
|
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]
|
trials = trials[base_metrics + param_metrics]
|
||||||
|
|
||||||
base_columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Median profit', 'Total profit',
|
base_columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Median profit', 'Total profit',
|
||||||
|
@ -360,7 +360,7 @@ class LocalTrade():
|
|||||||
if self.has_no_leverage:
|
if self.has_no_leverage:
|
||||||
return 0.0
|
return 0.0
|
||||||
elif not self.is_short:
|
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:
|
else:
|
||||||
return self.amount
|
return self.amount
|
||||||
|
|
||||||
@ -747,7 +747,7 @@ class LocalTrade():
|
|||||||
now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None)
|
now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None)
|
||||||
sec_per_hour = Decimal(3600)
|
sec_per_hour = Decimal(3600)
|
||||||
total_seconds = Decimal((now - open_date).total_seconds())
|
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)
|
rate = Decimal(interest_rate or self.interest_rate)
|
||||||
borrowed = Decimal(self.borrowed)
|
borrowed = Decimal(self.borrowed)
|
||||||
@ -861,9 +861,9 @@ class LocalTrade():
|
|||||||
return 0.0
|
return 0.0
|
||||||
else:
|
else:
|
||||||
if self.is_short:
|
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:
|
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}")
|
return float(f"{profit_ratio:.8f}")
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ class VolatilityFilter(IPairList):
|
|||||||
returns = (np.log(daily_candles.close / daily_candles.close.shift(-1)))
|
returns = (np.log(daily_candles.close / daily_candles.close.shift(-1)))
|
||||||
returns.fillna(0, inplace=True)
|
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()
|
volatility_avg = volatility_series.mean()
|
||||||
|
|
||||||
if self._min_volatility <= volatility_avg <= self._max_volatility:
|
if self._min_volatility <= volatility_avg <= self._max_volatility:
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends
|
from fastapi import APIRouter, BackgroundTasks, Depends
|
||||||
|
|
||||||
from freqtrade.configuration.config_validation import validate_config_consistency
|
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.enums import BacktestState
|
||||||
from freqtrade.exceptions import DependencyException
|
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.deps import get_config, is_webserver_mode
|
||||||
from freqtrade.rpc.api_server.webserver import ApiServer
|
from freqtrade.rpc.api_server.webserver import ApiServer
|
||||||
from freqtrade.rpc.rpc import RPCException
|
from freqtrade.rpc.rpc import RPCException
|
||||||
@ -200,3 +203,30 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)):
|
|||||||
"progress": 0,
|
"progress": 0,
|
||||||
"status_msg": "Backtest ended",
|
"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,
|
||||||
|
}
|
||||||
|
@ -421,6 +421,13 @@ class BacktestResponse(BaseModel):
|
|||||||
backtest_result: Optional[Dict[str, Any]]
|
backtest_result: Optional[Dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
|
class BacktestHistoryEntry(BaseModel):
|
||||||
|
filename: str
|
||||||
|
strategy: str
|
||||||
|
run_id: str
|
||||||
|
backtest_start_time: int
|
||||||
|
|
||||||
|
|
||||||
class SysInfo(BaseModel):
|
class SysInfo(BaseModel):
|
||||||
cpu_pct: List[float]
|
cpu_pct: List[float]
|
||||||
ram_pct: float
|
ram_pct: float
|
||||||
|
@ -35,7 +35,8 @@ logger = logging.getLogger(__name__)
|
|||||||
# 1.13: forcebuy supports stake_amount
|
# 1.13: forcebuy supports stake_amount
|
||||||
# versions 2.xx -> futures/short branch
|
# versions 2.xx -> futures/short branch
|
||||||
# 2.14: Add entry/exit orders to trade response
|
# 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.
|
# Public API, requires no auth.
|
||||||
router_public = APIRouter()
|
router_public = APIRouter()
|
||||||
|
@ -387,7 +387,7 @@ class Telegram(RPCHandler):
|
|||||||
else:
|
else:
|
||||||
return "\N{CROSS MARK}"
|
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
|
Prepare details of trade with entry adjustment enabled
|
||||||
"""
|
"""
|
||||||
@ -405,7 +405,7 @@ class Telegram(RPCHandler):
|
|||||||
if x == 0:
|
if x == 0:
|
||||||
lines.append(f"*Entry #{x+1}:*")
|
lines.append(f"*Entry #{x+1}:*")
|
||||||
lines.append(
|
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}")
|
lines.append(f"*Average Entry Price:* {cur_entry_average}")
|
||||||
else:
|
else:
|
||||||
sumA = 0
|
sumA = 0
|
||||||
@ -419,7 +419,8 @@ class Telegram(RPCHandler):
|
|||||||
if prev_avg_price:
|
if prev_avg_price:
|
||||||
minus_on_entry = (cur_entry_average - prev_avg_price) / 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
|
days = dur_entry.days
|
||||||
hours, remainder = divmod(dur_entry.seconds, 3600)
|
hours, remainder = divmod(dur_entry.seconds, 3600)
|
||||||
minutes, seconds = divmod(remainder, 60)
|
minutes, seconds = divmod(remainder, 60)
|
||||||
@ -428,7 +429,7 @@ class Telegram(RPCHandler):
|
|||||||
lines.append("({})".format(cur_entry_datetime
|
lines.append("({})".format(cur_entry_datetime
|
||||||
.humanize(granularity=["day", "hour", "minute"])))
|
.humanize(granularity=["day", "hour", "minute"])))
|
||||||
lines.append(
|
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} "
|
lines.append(f"*Average Entry Price:* {cur_entry_average} "
|
||||||
f"({price_to_1st_entry:.2%} from 1st entry rate)")
|
f"({price_to_1st_entry:.2%} from 1st entry rate)")
|
||||||
lines.append(f"*Order filled at:* {order['order_filled_date']}")
|
lines.append(f"*Order filled at:* {order['order_filled_date']}")
|
||||||
@ -471,7 +472,7 @@ class Telegram(RPCHandler):
|
|||||||
"*Current Pair:* {pair}",
|
"*Current Pair:* {pair}",
|
||||||
"*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"),
|
"*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"),
|
||||||
"*Leverage:* `{leverage}`" if r.get('leverage') else "",
|
"*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 "",
|
"*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "",
|
||||||
"*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "",
|
"*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "",
|
||||||
]
|
]
|
||||||
|
@ -206,18 +206,18 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
"""
|
"""
|
||||||
pass
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
DEPRECATED: Please use `check_entry_timeout` instead.
|
DEPRECATED: Please use `check_entry_timeout` instead.
|
||||||
"""
|
"""
|
||||||
return False
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Check entry timeout function callback.
|
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,
|
It is called whenever a limit entry order has been created,
|
||||||
and is not yet fully filled.
|
and is not yet fully filled.
|
||||||
Configuration options in `unfilledtimeout` will be verified before this,
|
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.
|
When not implemented by a strategy, this simply returns False.
|
||||||
:param pair: Pair the trade is for
|
:param pair: Pair the trade is for
|
||||||
:param trade: trade object.
|
:param trade: Trade object.
|
||||||
:param order: Order dictionary as returned from CCXT.
|
:param order: Order object.
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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.
|
: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.
|
: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(
|
return self.check_buy_timeout(
|
||||||
pair=pair, trade=trade, order=order, current_time=current_time)
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
DEPRECATED: Please use `check_exit_timeout` instead.
|
DEPRECATED: Please use `check_exit_timeout` instead.
|
||||||
"""
|
"""
|
||||||
return False
|
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:
|
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.
|
This method can be used to override the exit-timeout.
|
||||||
It is called whenever a (long) limit sell order or (short) limit buy
|
It is called whenever a limit exit order has been created,
|
||||||
has been created, and is not yet fully filled.
|
and is not yet fully filled.
|
||||||
Configuration options in `unfilledtimeout` will be verified before this,
|
Configuration options in `unfilledtimeout` will be verified before this,
|
||||||
so ensure to set these timeouts high enough.
|
so ensure to set these timeouts high enough.
|
||||||
|
|
||||||
When not implemented by a strategy, this simply returns False.
|
When not implemented by a strategy, this simply returns False.
|
||||||
:param pair: Pair the trade is for
|
:param pair: Pair the trade is for
|
||||||
:param trade: trade object.
|
:param trade: Trade object.
|
||||||
:param order: Order dictionary as returned from CCXT.
|
:param order: Order object
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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.
|
: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(
|
return self.check_sell_timeout(
|
||||||
pair=pair, trade=trade, order=order, current_time=current_time)
|
pair=pair, trade=trade, order=order, current_time=current_time)
|
||||||
|
@ -93,9 +93,9 @@ def stoploss_from_open(
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
if is_short is True:
|
if is_short is True:
|
||||||
stoploss = -1+((1-open_relative_stop)/(1-current_profit))
|
stoploss = -1 + ((1 - open_relative_stop) / (1 - current_profit))
|
||||||
else:
|
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
|
# negative stoploss values indicate the requested stop price is higher/lower
|
||||||
# (long/short) than the current price
|
# (long/short) than the current price
|
||||||
|
@ -191,7 +191,8 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
|
|||||||
"""
|
"""
|
||||||
return True
|
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.
|
Check entry timeout function callback.
|
||||||
This method can be used to override the entry-timeout.
|
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.
|
When not implemented by a strategy, this simply returns False.
|
||||||
:param pair: Pair the trade is for
|
:param pair: Pair the trade is for
|
||||||
:param trade: trade object.
|
:param trade: Trade object.
|
||||||
:param order: Order dictionary as returned from CCXT.
|
: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.
|
: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
|
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.
|
Check exit timeout function callback.
|
||||||
This method can be used to override the exit-timeout.
|
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.
|
When not implemented by a strategy, this simply returns False.
|
||||||
:param pair: Pair the trade is for
|
:param pair: Pair the trade is for
|
||||||
:param trade: trade object.
|
:param trade: Trade object.
|
||||||
:param order: Order dictionary as returned from CCXT.
|
: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.
|
: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.
|
:return bool: When True is returned, then the exit-order is cancelled.
|
||||||
"""
|
"""
|
||||||
|
@ -39,7 +39,9 @@ console_scripts =
|
|||||||
freqtrade = freqtrade.main:main
|
freqtrade = freqtrade.main:main
|
||||||
|
|
||||||
[flake8]
|
[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-line-length = 100
|
||||||
max-complexity = 12
|
max-complexity = 12
|
||||||
exclude =
|
exclude =
|
||||||
|
@ -1429,7 +1429,7 @@ def test_backtesting_show(mocker, testdatadir, capsys):
|
|||||||
args = [
|
args = [
|
||||||
"backtesting-show",
|
"backtesting-show",
|
||||||
"--export-filename",
|
"--export-filename",
|
||||||
f"{testdatadir / 'backtest-result_new.json'}",
|
f"{testdatadir / 'backtest_results/backtest-result_new.json'}",
|
||||||
"--show-pair-list"
|
"--show-pair-list"
|
||||||
]
|
]
|
||||||
pargs = get_args(args)
|
pargs = get_args(args)
|
||||||
|
@ -27,18 +27,19 @@ def test_get_latest_backtest_filename(testdatadir, mocker):
|
|||||||
|
|
||||||
with pytest.raises(ValueError,
|
with pytest.raises(ValueError,
|
||||||
match=r"Directory .* does not seem to contain .*"):
|
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'
|
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'
|
assert res == 'backtest-result_new.json'
|
||||||
|
|
||||||
mocker.patch("freqtrade.data.btanalysis.json_load", return_value={})
|
mocker.patch("freqtrade.data.btanalysis.json_load", return_value={})
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"Invalid '.last_result.json' format."):
|
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):
|
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):
|
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)
|
bt_data = load_backtest_data(filename)
|
||||||
assert isinstance(bt_data, DataFrame)
|
assert isinstance(bt_data, DataFrame)
|
||||||
assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp'])
|
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)
|
assert bt_data.equals(bt_data2)
|
||||||
|
|
||||||
# Test loading from folder (must yield same result)
|
# 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)
|
assert bt_data.equals(bt_data3)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"File .* does not exist\."):
|
with pytest.raises(ValueError, match=r"File .* does not exist\."):
|
||||||
load_backtest_data(str("filename") + "nofile")
|
load_backtest_data(str("filename") + "nofile")
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"Unknown dataformat."):
|
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):
|
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'):
|
for strategy in ('StrategyTestV2', 'TestStrategy'):
|
||||||
bt_data = load_backtest_data(filename, strategy=strategy)
|
bt_data = load_backtest_data(filename, strategy=strategy)
|
||||||
assert isinstance(bt_data, DataFrame)
|
assert isinstance(bt_data, DataFrame)
|
||||||
@ -182,7 +183,7 @@ def test_extract_trades_of_period(testdatadir):
|
|||||||
|
|
||||||
|
|
||||||
def test_analyze_trade_parallelism(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)
|
bt_data = load_backtest_data(filename)
|
||||||
|
|
||||||
res = analyze_trade_parallelism(bt_data, "5m")
|
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):
|
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)
|
bt_data = load_backtest_data(filename)
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||||
|
|
||||||
@ -272,7 +273,7 @@ def test_create_cum_profit(testdatadir):
|
|||||||
|
|
||||||
|
|
||||||
def test_create_cum_profit1(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)
|
bt_data = load_backtest_data(filename)
|
||||||
# Move close-time to "off" the candle, to make sure the logic still works
|
# 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)
|
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):
|
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)
|
bt_data = load_backtest_data(filename)
|
||||||
_, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown(
|
_, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown(
|
||||||
bt_data, value_col="profit_abs")
|
bt_data, value_col="profit_abs")
|
||||||
@ -318,7 +319,7 @@ def test_calculate_max_drawdown(testdatadir):
|
|||||||
|
|
||||||
|
|
||||||
def test_calculate_csum(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)
|
bt_data = load_backtest_data(filename)
|
||||||
csum_min, csum_max = calculate_csum(bt_data)
|
csum_min, csum_max = calculate_csum(bt_data)
|
||||||
|
|
||||||
|
@ -231,6 +231,10 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
|
|||||||
(2.34559, 2, 3, 1, 2.345, 'spot'),
|
(2.34559, 2, 3, 1, 2.345, 'spot'),
|
||||||
(2.9999, 2, 3, 1, 2.999, 'spot'),
|
(2.9999, 2, 3, 1, 2.999, 'spot'),
|
||||||
(2.9909, 2, 3, 1, 2.990, '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
|
# Tests for Tick-size
|
||||||
(2.34559, 4, 0.0001, 1, 2.3455, 'spot'),
|
(2.34559, 4, 0.0001, 1, 2.3455, 'spot'),
|
||||||
(2.34559, 4, 0.00001, 1, 2.34559, '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
|
# min
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
|
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)
|
assert isclose(result, expected_result)
|
||||||
# With Leverage
|
# With Leverage
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0)
|
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
|
# max
|
||||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||||
assert result == 10000
|
assert result == 10000
|
||||||
@ -401,11 +405,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
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)
|
assert isclose(result, expected_result)
|
||||||
# With Leverage
|
# With Leverage
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
|
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
|
# max
|
||||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||||
assert result == 20000
|
assert result == 20000
|
||||||
@ -420,11 +424,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
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)
|
assert isclose(result, expected_result)
|
||||||
# With Leverage
|
# With Leverage
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
|
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)
|
# min amount and cost are set (amount is minial)
|
||||||
markets["ETH/BTC"]["limits"] = {
|
markets["ETH/BTC"]["limits"] = {
|
||||||
@ -436,11 +440,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
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)
|
assert isclose(result, expected_result)
|
||||||
# With Leverage
|
# With Leverage
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0)
|
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
|
# Max
|
||||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||||
assert result == 1000
|
assert result == 1000
|
||||||
@ -450,7 +454,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
|||||||
assert isclose(result, expected_result)
|
assert isclose(result, expected_result)
|
||||||
# With Leverage
|
# With Leverage
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0)
|
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
|
# Max
|
||||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||||
assert result == 1000
|
assert result == 1000
|
||||||
@ -461,7 +465,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
|||||||
assert isclose(result, expected_result)
|
assert isclose(result, expected_result)
|
||||||
# With Leverage
|
# With Leverage
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
|
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
|
# Max
|
||||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||||
assert result == 1000
|
assert result == 1000
|
||||||
@ -489,7 +493,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
|||||||
)
|
)
|
||||||
# With Leverage, Contract size 10
|
# With Leverage, Contract size 10
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
|
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
|
# Max
|
||||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||||
assert result == 10000
|
assert result == 10000
|
||||||
@ -510,7 +514,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss)
|
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)
|
assert round(result, 8) == round(expected_result, 8)
|
||||||
# Max
|
# Max
|
||||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2.0)
|
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
|
# Leverage
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0)
|
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
|
# Contract_size
|
||||||
markets["ETH/BTC"]["contractSize"] = 0.1
|
markets["ETH/BTC"]["contractSize"] = 0.1
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0)
|
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
|
# Max
|
||||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 12.0)
|
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
|
# Monkey-patch async function
|
||||||
exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
ret = await exchange._async_get_trade_history_time(pair,
|
ret = await exchange._async_get_trade_history_time(
|
||||||
since=fetch_trades_result[0]['timestamp'],
|
pair,
|
||||||
until=fetch_trades_result[-1]['timestamp']-1)
|
since=fetch_trades_result[0]['timestamp'],
|
||||||
|
until=fetch_trades_result[-1]['timestamp'] - 1)
|
||||||
assert type(ret) is tuple
|
assert type(ret) is tuple
|
||||||
assert ret[0] == pair
|
assert ret[0] == pair
|
||||||
assert type(ret[1]) is list
|
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)
|
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0][0],
|
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 type(ret) is tuple
|
||||||
assert ret[0] == pair
|
assert ret[0] == pair
|
||||||
assert type(ret[1]) is list
|
assert type(ret[1]) is list
|
||||||
|
@ -6,7 +6,7 @@ import pytest
|
|||||||
from freqtrade.leverage import interest
|
from freqtrade.leverage import interest
|
||||||
|
|
||||||
|
|
||||||
ten_mins = Decimal(1/6)
|
ten_mins = Decimal(1 / 6)
|
||||||
five_hours = Decimal(5.0)
|
five_hours = Decimal(5.0)
|
||||||
twentyfive_hours = Decimal(25.0)
|
twentyfive_hours = Decimal(25.0)
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ def test_store_backtest_stats(testdatadir, mocker):
|
|||||||
|
|
||||||
assert dump_mock.call_count == 3
|
assert dump_mock.call_count == 3
|
||||||
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
|
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()
|
dump_mock.reset_mock()
|
||||||
filename = testdatadir / 'testresult.json'
|
filename = testdatadir / 'testresult.json'
|
||||||
@ -228,7 +228,7 @@ def test_generate_pair_metrics():
|
|||||||
|
|
||||||
def test_generate_daily_stats(testdatadir):
|
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)
|
bt_data = load_backtest_data(filename)
|
||||||
res = generate_daily_stats(bt_data)
|
res = generate_daily_stats(bt_data)
|
||||||
assert isinstance(res, dict)
|
assert isinstance(res, dict)
|
||||||
@ -248,7 +248,7 @@ def test_generate_daily_stats(testdatadir):
|
|||||||
|
|
||||||
|
|
||||||
def test_generate_trading_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)
|
bt_data = load_backtest_data(filename)
|
||||||
res = generate_trading_stats(bt_data)
|
res = generate_trading_stats(bt_data)
|
||||||
assert isinstance(res, dict)
|
assert isinstance(res, dict)
|
||||||
@ -332,7 +332,7 @@ def test_generate_sell_reason_stats():
|
|||||||
|
|
||||||
|
|
||||||
def test_text_table_strategy(testdatadir):
|
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 = load_backtest_stats(filename)
|
||||||
|
|
||||||
bt_res_data_comparison = bt_res_data.pop('strategy_comparison')
|
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):
|
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')
|
bt_data = load_backtest_data(filename).to_dict(orient='records')
|
||||||
|
|
||||||
res = generate_periodic_breakdown_stats(bt_data, 'day')
|
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):
|
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)
|
bt_data = load_backtest_stats(filename)
|
||||||
default_conf['backtest_show_pair_list'] = True
|
default_conf['backtest_show_pair_list'] = True
|
||||||
|
|
||||||
|
@ -1581,6 +1581,38 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
|
|||||||
assert result['status_msg'] == 'Backtest reset'
|
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):
|
def test_health(botclient):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
|
|
||||||
|
@ -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(90, 100, True)) == 0
|
||||||
assert pytest.approx(stoploss_from_absolute(100, 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(110, 100, True)) == 0.1
|
||||||
assert pytest.approx(stoploss_from_absolute(105, 100, True)) == 0.05
|
assert pytest.approx(stoploss_from_absolute(105, 100, True)) == 0.05
|
||||||
assert pytest.approx(stoploss_from_absolute(100, 0, True)) == 1
|
assert pytest.approx(stoploss_from_absolute(100, 0, True)) == 1
|
||||||
|
@ -119,7 +119,7 @@ def test_set_stop_loss_isolated_liq(fee):
|
|||||||
assert trade.stop_loss is None
|
assert trade.stop_loss is None
|
||||||
assert trade.initial_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.liquidation_price == 0.09
|
||||||
assert trade.stop_loss == 0.1
|
assert trade.stop_loss == 0.1
|
||||||
assert trade.initial_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.stop_loss is None
|
||||||
assert trade.initial_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.liquidation_price == 0.09
|
||||||
assert trade.stop_loss == 0.08
|
assert trade.stop_loss == 0.08
|
||||||
assert trade.initial_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
|
assert trade.initial_stop_loss == 0.08
|
||||||
|
|
||||||
trade.set_isolated_liq(0.07)
|
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.liquidation_price == 0.07
|
||||||
assert trade.stop_loss == 0.07
|
assert trade.stop_loss == 0.07
|
||||||
assert trade.initial_stop_loss == 0.08
|
assert trade.initial_stop_loss == 0.08
|
||||||
|
|
||||||
# Stop doesn't move stop higher
|
# 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.liquidation_price == 0.07
|
||||||
assert trade.stop_loss == 0.07
|
assert trade.stop_loss == 0.07
|
||||||
assert trade.initial_stop_loss == 0.08
|
assert trade.initial_stop_loss == 0.08
|
||||||
|
@ -157,7 +157,7 @@ def test_plot_trades(testdatadir, caplog):
|
|||||||
assert fig == fig1
|
assert fig == fig1
|
||||||
assert log_has("No trades found.", caplog)
|
assert log_has("No trades found.", caplog)
|
||||||
pair = "ADA/BTC"
|
pair = "ADA/BTC"
|
||||||
filename = testdatadir / "backtest-result_new.json"
|
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||||
trades = load_backtest_data(filename)
|
trades = load_backtest_data(filename)
|
||||||
trades = trades.loc[trades['pair'] == pair]
|
trades = trades.loc[trades['pair'] == pair]
|
||||||
|
|
||||||
@ -298,7 +298,7 @@ def test_generate_plot_file(mocker, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_add_profit(testdatadir):
|
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)
|
bt_data = load_backtest_data(filename)
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||||
|
|
||||||
@ -318,7 +318,7 @@ def test_add_profit(testdatadir):
|
|||||||
|
|
||||||
|
|
||||||
def test_generate_profit_graph(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)
|
trades = load_backtest_data(filename)
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||||
pairs = ["TRX/BTC", "XLM/BTC"]
|
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.*"):
|
match=r"No trades found, cannot generate Profit-plot.*"):
|
||||||
plot_profit(default_conf)
|
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)
|
plot_profit(default_conf)
|
||||||
|
|
||||||
|
10
tests/testdata/backtest_results/backtest-result_multistrat.meta.json
vendored
Normal file
10
tests/testdata/backtest_results/backtest-result_multistrat.meta.json
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"StrategyTestV2": {
|
||||||
|
"run_id": "430d0271075ef327edbb23088f4db4ebe51a3dbf",
|
||||||
|
"backtest_start_time": 1648904006
|
||||||
|
},
|
||||||
|
"TestStrategy": {
|
||||||
|
"run_id": "110d0271075ef327edbb23085102b4ebe51a3d55",
|
||||||
|
"backtest_start_time": 1648904006
|
||||||
|
}
|
||||||
|
}
|
6
tests/testdata/backtest_results/backtest-result_new.meta.json
vendored
Normal file
6
tests/testdata/backtest_results/backtest-result_new.meta.json
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"StrategyTestV3": {
|
||||||
|
"run_id": "430d0271075ef327edbb23088f4db4ebe51a3dbf",
|
||||||
|
"backtest_start_time": 1648904006
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user