Merge branch 'develop' into feat_readjust_entry

This commit is contained in:
eSeR1805 2022-04-16 15:20:50 +03:00
commit d65b64a46f
No known key found for this signature in database
GPG Key ID: BA53686259B46936
35 changed files with 247 additions and 110 deletions

View File

@ -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.
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.

View File

@ -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
```

View File

@ -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
```

View File

@ -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"

View File

@ -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

View File

@ -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,

View File

@ -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,
}

View File

@ -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

View File

@ -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()

View File

@ -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 "",
]

View File

@ -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)

View File

@ -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.
"""

View File

@ -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 =

View File

@ -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)

View File

@ -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)

View File

@ -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'),
@ -2691,7 +2695,8 @@ 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,
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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,10 @@
{
"StrategyTestV2": {
"run_id": "430d0271075ef327edbb23088f4db4ebe51a3dbf",
"backtest_start_time": 1648904006
},
"TestStrategy": {
"run_id": "110d0271075ef327edbb23085102b4ebe51a3d55",
"backtest_start_time": 1648904006
}
}

View File

@ -0,0 +1,6 @@
{
"StrategyTestV3": {
"run_id": "430d0271075ef327edbb23088f4db4ebe51a3dbf",
"backtest_start_time": 1648904006
}
}