Merge pull request #4268 from freqtrade/backtest_trade_object
Backtest trade object
This commit is contained in:
commit
65459086a3
@ -63,7 +63,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
|
|||||||
* 0.25: Avoiding trade loss
|
* 0.25: Avoiding trade loss
|
||||||
* 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above
|
* 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above
|
||||||
"""
|
"""
|
||||||
total_profit = results['profit_percent'].sum()
|
total_profit = results['profit_ratio'].sum()
|
||||||
trade_duration = results['trade_duration'].mean()
|
trade_duration = results['trade_duration'].mean()
|
||||||
|
|
||||||
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
|
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
|
||||||
@ -77,10 +77,10 @@ Currently, the arguments are:
|
|||||||
|
|
||||||
* `results`: DataFrame containing the result
|
* `results`: DataFrame containing the result
|
||||||
The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`):
|
The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`):
|
||||||
`pair, profit_percent, profit_abs, open_date, open_rate, open_fee, close_date, close_rate, close_fee, amount, trade_duration, open_at_end, sell_reason`
|
`pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date, close_rate, fee_close, amount, trade_duration, is_open, sell_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs`
|
||||||
* `trade_count`: Amount of trades (identical to `len(results)`)
|
* `trade_count`: Amount of trades (identical to `len(results)`)
|
||||||
* `min_date`: Start date of the hyperopting TimeFrame
|
* `min_date`: Start date of the timerange used
|
||||||
* `min_date`: End date of the hyperopting TimeFrame
|
* `min_date`: End date of the timerange used
|
||||||
|
|
||||||
This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you.
|
This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you.
|
||||||
|
|
||||||
|
@ -262,9 +262,9 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
```
|
```
|
||||||
|
|
||||||
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
|
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
|
||||||
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - to clearly see settings for this.
|
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
|
||||||
- `Total trades`: Identical to the total trades of the backtest output table.
|
- `Total trades`: Identical to the total trades of the backtest output table.
|
||||||
- `Total Profit %`: Total profit per stake amount. Aligned to the TOTAL column of the first table.
|
- `Total Profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table.
|
||||||
- `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
|
- `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
|
||||||
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`.
|
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`.
|
||||||
- `Best Trade` / `Worst Trade`: Biggest winning trade and biggest losing trade
|
- `Best Trade` / `Worst Trade`: Biggest winning trade and biggest losing trade
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
Helpers when analyzing backtest data
|
Helpers when analyzing backtest data
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timezone
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Optional, Tuple, Union
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -16,9 +15,22 @@ from freqtrade.persistence import Trade, init_db
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# must align with columns in backtest.py
|
# Old format - maybe remove?
|
||||||
BT_DATA_COLUMNS = ["pair", "profit_percent", "open_date", "close_date", "index", "trade_duration",
|
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
|
||||||
"open_rate", "close_rate", "open_at_end", "sell_reason"]
|
"trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"]
|
||||||
|
|
||||||
|
# Mid-term format, crated by BacktestResult Named Tuple
|
||||||
|
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
|
||||||
|
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
|
||||||
|
'fee_close', 'amount', 'profit_abs', 'profit_ratio']
|
||||||
|
|
||||||
|
# Newest format
|
||||||
|
BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
|
||||||
|
'open_rate', 'close_rate',
|
||||||
|
'fee_open', 'fee_close', 'trade_duration',
|
||||||
|
'profit_ratio', 'profit_abs', 'sell_reason',
|
||||||
|
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
|
||||||
|
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', ]
|
||||||
|
|
||||||
|
|
||||||
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
|
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
|
||||||
@ -154,7 +166,7 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# old format - only with lists.
|
# old format - only with lists.
|
||||||
df = pd.DataFrame(data, columns=BT_DATA_COLUMNS)
|
df = pd.DataFrame(data, columns=BT_DATA_COLUMNS_OLD)
|
||||||
|
|
||||||
df['open_date'] = pd.to_datetime(df['open_date'],
|
df['open_date'] = pd.to_datetime(df['open_date'],
|
||||||
unit='s',
|
unit='s',
|
||||||
@ -166,7 +178,10 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
|
|||||||
utc=True,
|
utc=True,
|
||||||
infer_datetime_format=True
|
infer_datetime_format=True
|
||||||
)
|
)
|
||||||
|
# Create compatibility with new format
|
||||||
df['profit_abs'] = df['close_rate'] - df['open_rate']
|
df['profit_abs'] = df['close_rate'] - df['open_rate']
|
||||||
|
if 'profit_ratio' not in df.columns:
|
||||||
|
df['profit_ratio'] = df['profit_percent']
|
||||||
df = df.sort_values("open_date").reset_index(drop=True)
|
df = df.sort_values("open_date").reset_index(drop=True)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
@ -209,6 +224,20 @@ def evaluate_result_multi(results: pd.DataFrame, timeframe: str,
|
|||||||
return df_final[df_final['open_trades'] > max_open_trades]
|
return df_final[df_final['open_trades'] > max_open_trades]
|
||||||
|
|
||||||
|
|
||||||
|
def trade_list_to_dataframe(trades: List[Trade]) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Convert list of Trade objects to pandas Dataframe
|
||||||
|
:param trades: List of trade objects
|
||||||
|
:return: Dataframe with BT_DATA_COLUMNS
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame.from_records([t.to_json() for t in trades], columns=BT_DATA_COLUMNS)
|
||||||
|
if len(df) > 0:
|
||||||
|
df.loc[:, 'close_date'] = pd.to_datetime(df['close_date'], utc=True)
|
||||||
|
df.loc[:, 'open_date'] = pd.to_datetime(df['open_date'], utc=True)
|
||||||
|
df.loc[:, 'close_rate'] = df['close_rate'].astype('float64')
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataFrame:
|
def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Load trades from a DB (using dburl)
|
Load trades from a DB (using dburl)
|
||||||
@ -219,36 +248,10 @@ def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataF
|
|||||||
"""
|
"""
|
||||||
init_db(db_url, clean_open_orders=False)
|
init_db(db_url, clean_open_orders=False)
|
||||||
|
|
||||||
columns = ["pair", "open_date", "close_date", "profit", "profit_percent",
|
|
||||||
"open_rate", "close_rate", "amount", "trade_duration", "sell_reason",
|
|
||||||
"fee_open", "fee_close", "open_rate_requested", "close_rate_requested",
|
|
||||||
"stake_amount", "max_rate", "min_rate", "id", "exchange",
|
|
||||||
"stop_loss", "initial_stop_loss", "strategy", "timeframe"]
|
|
||||||
|
|
||||||
filters = []
|
filters = []
|
||||||
if strategy:
|
if strategy:
|
||||||
filters.append(Trade.strategy == strategy)
|
filters.append(Trade.strategy == strategy)
|
||||||
|
trades = trade_list_to_dataframe(Trade.get_trades(filters).all())
|
||||||
trades = pd.DataFrame([(t.pair,
|
|
||||||
t.open_date.replace(tzinfo=timezone.utc),
|
|
||||||
t.close_date.replace(tzinfo=timezone.utc) if t.close_date else None,
|
|
||||||
t.calc_profit(), t.calc_profit_ratio(),
|
|
||||||
t.open_rate, t.close_rate, t.amount,
|
|
||||||
(round((t.close_date.timestamp() - t.open_date.timestamp()) / 60, 2)
|
|
||||||
if t.close_date else None),
|
|
||||||
t.sell_reason,
|
|
||||||
t.fee_open, t.fee_close,
|
|
||||||
t.open_rate_requested,
|
|
||||||
t.close_rate_requested,
|
|
||||||
t.stake_amount,
|
|
||||||
t.max_rate,
|
|
||||||
t.min_rate,
|
|
||||||
t.id, t.exchange,
|
|
||||||
t.stop_loss, t.initial_stop_loss,
|
|
||||||
t.strategy, t.timeframe
|
|
||||||
)
|
|
||||||
for t in Trade.get_trades(filters).all()],
|
|
||||||
columns=columns)
|
|
||||||
|
|
||||||
return trades
|
return trades
|
||||||
|
|
||||||
|
@ -7,13 +7,14 @@ import logging
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.configuration import TimeRange, remove_credentials, validate_config_consistency
|
from freqtrade.configuration import TimeRange, remove_credentials, validate_config_consistency
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
|
from freqtrade.data.btanalysis import trade_list_to_dataframe
|
||||||
from freqtrade.data.converter import trim_dataframe
|
from freqtrade.data.converter import trim_dataframe
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
@ -41,25 +42,6 @@ LOW_IDX = 5
|
|||||||
HIGH_IDX = 6
|
HIGH_IDX = 6
|
||||||
|
|
||||||
|
|
||||||
class BacktestResult(NamedTuple):
|
|
||||||
"""
|
|
||||||
NamedTuple Defining BacktestResults inputs.
|
|
||||||
"""
|
|
||||||
pair: str
|
|
||||||
profit_percent: float
|
|
||||||
profit_abs: float
|
|
||||||
open_date: datetime
|
|
||||||
open_rate: float
|
|
||||||
open_fee: float
|
|
||||||
close_date: datetime
|
|
||||||
close_rate: float
|
|
||||||
close_fee: float
|
|
||||||
amount: float
|
|
||||||
trade_duration: float
|
|
||||||
open_at_end: bool
|
|
||||||
sell_reason: SellType
|
|
||||||
|
|
||||||
|
|
||||||
class Backtesting:
|
class Backtesting:
|
||||||
"""
|
"""
|
||||||
Backtesting class, this class contains all the logic to run a backtest
|
Backtesting class, this class contains all the logic to run a backtest
|
||||||
@ -264,7 +246,7 @@ class Backtesting:
|
|||||||
else:
|
else:
|
||||||
return sell_row[OPEN_IDX]
|
return sell_row[OPEN_IDX]
|
||||||
|
|
||||||
def _get_sell_trade_entry(self, trade: Trade, sell_row: Tuple) -> Optional[BacktestResult]:
|
def _get_sell_trade_entry(self, trade: Trade, sell_row: Tuple) -> Optional[Trade]:
|
||||||
|
|
||||||
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], sell_row[DATE_IDX],
|
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], sell_row[DATE_IDX],
|
||||||
sell_row[BUY_IDX], sell_row[SELL_IDX],
|
sell_row[BUY_IDX], sell_row[SELL_IDX],
|
||||||
@ -276,25 +258,12 @@ class Backtesting:
|
|||||||
trade.close_date = sell_row[DATE_IDX]
|
trade.close_date = sell_row[DATE_IDX]
|
||||||
trade.sell_reason = sell.sell_type
|
trade.sell_reason = sell.sell_type
|
||||||
trade.close(closerate, show_msg=False)
|
trade.close(closerate, show_msg=False)
|
||||||
|
return trade
|
||||||
|
|
||||||
return BacktestResult(pair=trade.pair,
|
|
||||||
profit_percent=trade.calc_profit_ratio(rate=closerate),
|
|
||||||
profit_abs=trade.calc_profit(rate=closerate),
|
|
||||||
open_date=trade.open_date,
|
|
||||||
open_rate=trade.open_rate,
|
|
||||||
open_fee=self.fee,
|
|
||||||
close_date=sell_row[DATE_IDX],
|
|
||||||
close_rate=closerate,
|
|
||||||
close_fee=self.fee,
|
|
||||||
amount=trade.amount,
|
|
||||||
trade_duration=trade_dur,
|
|
||||||
open_at_end=False,
|
|
||||||
sell_reason=sell.sell_type
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def handle_left_open(self, open_trades: Dict[str, List[Trade]],
|
def handle_left_open(self, open_trades: Dict[str, List[Trade]],
|
||||||
data: Dict[str, List[Tuple]]) -> List[BacktestResult]:
|
data: Dict[str, List[Tuple]]) -> List[Trade]:
|
||||||
"""
|
"""
|
||||||
Handling of left open trades at the end of backtesting
|
Handling of left open trades at the end of backtesting
|
||||||
"""
|
"""
|
||||||
@ -304,24 +273,11 @@ class Backtesting:
|
|||||||
for trade in open_trades[pair]:
|
for trade in open_trades[pair]:
|
||||||
sell_row = data[pair][-1]
|
sell_row = data[pair][-1]
|
||||||
|
|
||||||
trade_entry = BacktestResult(pair=trade.pair,
|
trade.close_date = sell_row[DATE_IDX]
|
||||||
profit_percent=trade.calc_profit_ratio(
|
trade.sell_reason = SellType.FORCE_SELL
|
||||||
rate=sell_row[OPEN_IDX]),
|
trade.close(sell_row[OPEN_IDX], show_msg=False)
|
||||||
profit_abs=trade.calc_profit(sell_row[OPEN_IDX]),
|
trade.is_open = True
|
||||||
open_date=trade.open_date,
|
trades.append(trade)
|
||||||
open_rate=trade.open_rate,
|
|
||||||
open_fee=self.fee,
|
|
||||||
close_date=sell_row[DATE_IDX],
|
|
||||||
close_rate=sell_row[OPEN_IDX],
|
|
||||||
close_fee=self.fee,
|
|
||||||
amount=trade.amount,
|
|
||||||
trade_duration=int((
|
|
||||||
sell_row[DATE_IDX] - trade.open_date
|
|
||||||
).total_seconds() // 60),
|
|
||||||
open_at_end=True,
|
|
||||||
sell_reason=SellType.FORCE_SELL
|
|
||||||
)
|
|
||||||
trades.append(trade_entry)
|
|
||||||
return trades
|
return trades
|
||||||
|
|
||||||
def backtest(self, processed: Dict, stake_amount: float,
|
def backtest(self, processed: Dict, stake_amount: float,
|
||||||
@ -348,7 +304,7 @@ class Backtesting:
|
|||||||
f"start_date: {start_date}, end_date: {end_date}, "
|
f"start_date: {start_date}, end_date: {end_date}, "
|
||||||
f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}"
|
f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}"
|
||||||
)
|
)
|
||||||
trades = []
|
trades: List[Trade] = []
|
||||||
self.prepare_backtest(enable_protections)
|
self.prepare_backtest(enable_protections)
|
||||||
|
|
||||||
# Use dict of lists with data for performance
|
# Use dict of lists with data for performance
|
||||||
@ -429,7 +385,7 @@ class Backtesting:
|
|||||||
|
|
||||||
trades += self.handle_left_open(open_trades, data=data)
|
trades += self.handle_left_open(open_trades, data=data)
|
||||||
|
|
||||||
return DataFrame.from_records(trades, columns=BacktestResult._fields)
|
return trade_list_to_dataframe(trades)
|
||||||
|
|
||||||
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange):
|
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange):
|
||||||
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
|
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
|
||||||
|
@ -42,7 +42,7 @@ class ShortTradeDurHyperOptLoss(IHyperOptLoss):
|
|||||||
* 0.25: Avoiding trade loss
|
* 0.25: Avoiding trade loss
|
||||||
* 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above
|
* 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above
|
||||||
"""
|
"""
|
||||||
total_profit = results['profit_percent'].sum()
|
total_profit = results['profit_ratio'].sum()
|
||||||
trade_duration = results['trade_duration'].mean()
|
trade_duration = results['trade_duration'].mean()
|
||||||
|
|
||||||
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
|
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
|
||||||
|
@ -574,20 +574,20 @@ class Hyperopt:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict:
|
def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict:
|
||||||
wins = len(backtesting_results[backtesting_results.profit_percent > 0])
|
wins = len(backtesting_results[backtesting_results['profit_ratio'] > 0])
|
||||||
draws = len(backtesting_results[backtesting_results.profit_percent == 0])
|
draws = len(backtesting_results[backtesting_results['profit_ratio'] == 0])
|
||||||
losses = len(backtesting_results[backtesting_results.profit_percent < 0])
|
losses = len(backtesting_results[backtesting_results['profit_ratio'] < 0])
|
||||||
return {
|
return {
|
||||||
'trade_count': len(backtesting_results.index),
|
'trade_count': len(backtesting_results.index),
|
||||||
'wins': wins,
|
'wins': wins,
|
||||||
'draws': draws,
|
'draws': draws,
|
||||||
'losses': losses,
|
'losses': losses,
|
||||||
'winsdrawslosses': f"{wins:>4} {draws:>4} {losses:>4}",
|
'winsdrawslosses': f"{wins:>4} {draws:>4} {losses:>4}",
|
||||||
'avg_profit': backtesting_results.profit_percent.mean() * 100.0,
|
'avg_profit': backtesting_results['profit_ratio'].mean() * 100.0,
|
||||||
'median_profit': backtesting_results.profit_percent.median() * 100.0,
|
'median_profit': backtesting_results['profit_ratio'].median() * 100.0,
|
||||||
'total_profit': backtesting_results.profit_abs.sum(),
|
'total_profit': backtesting_results['profit_abs'].sum(),
|
||||||
'profit': backtesting_results.profit_percent.sum() * 100.0,
|
'profit': backtesting_results['profit_ratio'].sum() * 100.0,
|
||||||
'duration': backtesting_results.trade_duration.mean(),
|
'duration': backtesting_results['trade_duration'].mean(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _format_results_explanation_string(self, results_metrics: Dict) -> str:
|
def _format_results_explanation_string(self, results_metrics: Dict) -> str:
|
||||||
|
@ -34,5 +34,5 @@ class OnlyProfitHyperOptLoss(IHyperOptLoss):
|
|||||||
"""
|
"""
|
||||||
Objective function, returns smaller number for better results.
|
Objective function, returns smaller number for better results.
|
||||||
"""
|
"""
|
||||||
total_profit = results['profit_percent'].sum()
|
total_profit = results['profit_ratio'].sum()
|
||||||
return 1 - total_profit / EXPECTED_MAX_PROFIT
|
return 1 - total_profit / EXPECTED_MAX_PROFIT
|
||||||
|
@ -28,7 +28,7 @@ class SharpeHyperOptLoss(IHyperOptLoss):
|
|||||||
|
|
||||||
Uses Sharpe Ratio calculation.
|
Uses Sharpe Ratio calculation.
|
||||||
"""
|
"""
|
||||||
total_profit = results["profit_percent"]
|
total_profit = results["profit_ratio"]
|
||||||
days_period = (max_date - min_date).days
|
days_period = (max_date - min_date).days
|
||||||
|
|
||||||
# adding slippage of 0.1% per trade
|
# adding slippage of 0.1% per trade
|
||||||
|
@ -34,9 +34,9 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
|
|||||||
annual_risk_free_rate = 0.0
|
annual_risk_free_rate = 0.0
|
||||||
risk_free_rate = annual_risk_free_rate / days_in_year
|
risk_free_rate = annual_risk_free_rate / days_in_year
|
||||||
|
|
||||||
# apply slippage per trade to profit_percent
|
# apply slippage per trade to profit_ratio
|
||||||
results.loc[:, 'profit_percent_after_slippage'] = \
|
results.loc[:, 'profit_ratio_after_slippage'] = \
|
||||||
results['profit_percent'] - slippage_per_trade_ratio
|
results['profit_ratio'] - slippage_per_trade_ratio
|
||||||
|
|
||||||
# create the index within the min_date and end max_date
|
# create the index within the min_date and end max_date
|
||||||
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
|
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
|
||||||
@ -44,10 +44,10 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
|
|||||||
|
|
||||||
sum_daily = (
|
sum_daily = (
|
||||||
results.resample(resample_freq, on='close_date').agg(
|
results.resample(resample_freq, on='close_date').agg(
|
||||||
{"profit_percent_after_slippage": sum}).reindex(t_index).fillna(0)
|
{"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
total_profit = sum_daily["profit_percent_after_slippage"] - risk_free_rate
|
total_profit = sum_daily["profit_ratio_after_slippage"] - risk_free_rate
|
||||||
expected_returns_mean = total_profit.mean()
|
expected_returns_mean = total_profit.mean()
|
||||||
up_stdev = total_profit.std()
|
up_stdev = total_profit.std()
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class SortinoHyperOptLoss(IHyperOptLoss):
|
|||||||
|
|
||||||
Uses Sortino Ratio calculation.
|
Uses Sortino Ratio calculation.
|
||||||
"""
|
"""
|
||||||
total_profit = results["profit_percent"]
|
total_profit = results["profit_ratio"]
|
||||||
days_period = (max_date - min_date).days
|
days_period = (max_date - min_date).days
|
||||||
|
|
||||||
# adding slippage of 0.1% per trade
|
# adding slippage of 0.1% per trade
|
||||||
@ -36,7 +36,7 @@ class SortinoHyperOptLoss(IHyperOptLoss):
|
|||||||
expected_returns_mean = total_profit.sum() / days_period
|
expected_returns_mean = total_profit.sum() / days_period
|
||||||
|
|
||||||
results['downside_returns'] = 0
|
results['downside_returns'] = 0
|
||||||
results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent']
|
results.loc[total_profit < 0, 'downside_returns'] = results['profit_ratio']
|
||||||
down_stdev = np.std(results['downside_returns'])
|
down_stdev = np.std(results['downside_returns'])
|
||||||
|
|
||||||
if down_stdev != 0:
|
if down_stdev != 0:
|
||||||
|
@ -36,9 +36,9 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
|
|||||||
days_in_year = 365
|
days_in_year = 365
|
||||||
minimum_acceptable_return = 0.0
|
minimum_acceptable_return = 0.0
|
||||||
|
|
||||||
# apply slippage per trade to profit_percent
|
# apply slippage per trade to profit_ratio
|
||||||
results.loc[:, 'profit_percent_after_slippage'] = \
|
results.loc[:, 'profit_ratio_after_slippage'] = \
|
||||||
results['profit_percent'] - slippage_per_trade_ratio
|
results['profit_ratio'] - slippage_per_trade_ratio
|
||||||
|
|
||||||
# create the index within the min_date and end max_date
|
# create the index within the min_date and end max_date
|
||||||
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
|
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
|
||||||
@ -46,17 +46,17 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
|
|||||||
|
|
||||||
sum_daily = (
|
sum_daily = (
|
||||||
results.resample(resample_freq, on='close_date').agg(
|
results.resample(resample_freq, on='close_date').agg(
|
||||||
{"profit_percent_after_slippage": sum}).reindex(t_index).fillna(0)
|
{"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
total_profit = sum_daily["profit_percent_after_slippage"] - minimum_acceptable_return
|
total_profit = sum_daily["profit_ratio_after_slippage"] - minimum_acceptable_return
|
||||||
expected_returns_mean = total_profit.mean()
|
expected_returns_mean = total_profit.mean()
|
||||||
|
|
||||||
sum_daily['downside_returns'] = 0
|
sum_daily['downside_returns'] = 0
|
||||||
sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit
|
sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit
|
||||||
total_downside = sum_daily['downside_returns']
|
total_downside = sum_daily['downside_returns']
|
||||||
# Here total_downside contains min(0, P - MAR) values,
|
# Here total_downside contains min(0, P - MAR) values,
|
||||||
# where P = sum_daily["profit_percent_after_slippage"]
|
# where P = sum_daily["profit_ratio_after_slippage"]
|
||||||
down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside))
|
down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside))
|
||||||
|
|
||||||
if down_stdev != 0:
|
if down_stdev != 0:
|
||||||
|
@ -58,14 +58,14 @@ def _generate_result_line(result: DataFrame, max_open_trades: int, first_column:
|
|||||||
"""
|
"""
|
||||||
Generate one result dict, with "first_column" as key.
|
Generate one result dict, with "first_column" as key.
|
||||||
"""
|
"""
|
||||||
profit_sum = result['profit_percent'].sum()
|
profit_sum = result['profit_ratio'].sum()
|
||||||
profit_total = profit_sum / max_open_trades
|
profit_total = profit_sum / max_open_trades
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'key': first_column,
|
'key': first_column,
|
||||||
'trades': len(result),
|
'trades': len(result),
|
||||||
'profit_mean': result['profit_percent'].mean() if len(result) > 0 else 0.0,
|
'profit_mean': result['profit_ratio'].mean() if len(result) > 0 else 0.0,
|
||||||
'profit_mean_pct': result['profit_percent'].mean() * 100.0 if len(result) > 0 else 0.0,
|
'profit_mean_pct': result['profit_ratio'].mean() * 100.0 if len(result) > 0 else 0.0,
|
||||||
'profit_sum': profit_sum,
|
'profit_sum': profit_sum,
|
||||||
'profit_sum_pct': round(profit_sum * 100.0, 2),
|
'profit_sum_pct': round(profit_sum * 100.0, 2),
|
||||||
'profit_total_abs': result['profit_abs'].sum(),
|
'profit_total_abs': result['profit_abs'].sum(),
|
||||||
@ -124,8 +124,8 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
|
|||||||
for reason, count in results['sell_reason'].value_counts().iteritems():
|
for reason, count in results['sell_reason'].value_counts().iteritems():
|
||||||
result = results.loc[results['sell_reason'] == reason]
|
result = results.loc[results['sell_reason'] == reason]
|
||||||
|
|
||||||
profit_mean = result['profit_percent'].mean()
|
profit_mean = result['profit_ratio'].mean()
|
||||||
profit_sum = result['profit_percent'].sum()
|
profit_sum = result['profit_ratio'].sum()
|
||||||
profit_total = profit_sum / max_open_trades
|
profit_total = profit_sum / max_open_trades
|
||||||
|
|
||||||
tabular_data.append(
|
tabular_data.append(
|
||||||
@ -150,7 +150,7 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
|
|||||||
def generate_strategy_metrics(all_results: Dict) -> List[Dict]:
|
def generate_strategy_metrics(all_results: Dict) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Generate summary per strategy
|
Generate summary per strategy
|
||||||
:param all_results: Dict of <Strategyname: BacktestResult> containing results for all strategies
|
:param all_results: Dict of <Strategyname: DataFrame> containing results for all strategies
|
||||||
:return: List of Dicts containing the metrics per Strategy
|
:return: List of Dicts containing the metrics per Strategy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -199,15 +199,15 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]:
|
|||||||
'winner_holding_avg': timedelta(),
|
'winner_holding_avg': timedelta(),
|
||||||
'loser_holding_avg': timedelta(),
|
'loser_holding_avg': timedelta(),
|
||||||
}
|
}
|
||||||
daily_profit = results.resample('1d', on='close_date')['profit_percent'].sum()
|
daily_profit = results.resample('1d', on='close_date')['profit_ratio'].sum()
|
||||||
worst = min(daily_profit)
|
worst = min(daily_profit)
|
||||||
best = max(daily_profit)
|
best = max(daily_profit)
|
||||||
winning_days = sum(daily_profit > 0)
|
winning_days = sum(daily_profit > 0)
|
||||||
draw_days = sum(daily_profit == 0)
|
draw_days = sum(daily_profit == 0)
|
||||||
losing_days = sum(daily_profit < 0)
|
losing_days = sum(daily_profit < 0)
|
||||||
|
|
||||||
winning_trades = results.loc[results['profit_percent'] > 0]
|
winning_trades = results.loc[results['profit_ratio'] > 0]
|
||||||
losing_trades = results.loc[results['profit_percent'] < 0]
|
losing_trades = results.loc[results['profit_ratio'] < 0]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'backtest_best_day': best,
|
'backtest_best_day': best,
|
||||||
@ -243,7 +243,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
|
|||||||
if not isinstance(results, DataFrame):
|
if not isinstance(results, DataFrame):
|
||||||
continue
|
continue
|
||||||
config = content['config']
|
config = content['config']
|
||||||
max_open_trades = config['max_open_trades']
|
max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
|
||||||
stake_currency = config['stake_currency']
|
stake_currency = config['stake_currency']
|
||||||
|
|
||||||
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
||||||
@ -253,7 +253,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
|
|||||||
results=results)
|
results=results)
|
||||||
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
||||||
max_open_trades=max_open_trades,
|
max_open_trades=max_open_trades,
|
||||||
results=results.loc[results['open_at_end']],
|
results=results.loc[results['is_open']],
|
||||||
skip_nan=True)
|
skip_nan=True)
|
||||||
daily_stats = generate_daily_stats(results)
|
daily_stats = generate_daily_stats(results)
|
||||||
best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'],
|
best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'],
|
||||||
@ -273,8 +273,8 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
|
|||||||
'sell_reason_summary': sell_reason_stats,
|
'sell_reason_summary': sell_reason_stats,
|
||||||
'left_open_trades': left_open_results,
|
'left_open_trades': left_open_results,
|
||||||
'total_trades': len(results),
|
'total_trades': len(results),
|
||||||
'profit_mean': results['profit_percent'].mean() if len(results) > 0 else 0,
|
'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0,
|
||||||
'profit_total': results['profit_percent'].sum(),
|
'profit_total': results['profit_ratio'].sum() / max_open_trades,
|
||||||
'profit_total_abs': results['profit_abs'].sum(),
|
'profit_total_abs': results['profit_abs'].sum(),
|
||||||
'backtest_start': min_date.datetime,
|
'backtest_start': min_date.datetime,
|
||||||
'backtest_start_ts': min_date.int_timestamp * 1000,
|
'backtest_start_ts': min_date.int_timestamp * 1000,
|
||||||
@ -290,8 +290,9 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
|
|||||||
'pairlist': list(btdata.keys()),
|
'pairlist': list(btdata.keys()),
|
||||||
'stake_amount': config['stake_amount'],
|
'stake_amount': config['stake_amount'],
|
||||||
'stake_currency': config['stake_currency'],
|
'stake_currency': config['stake_currency'],
|
||||||
'max_open_trades': (config['max_open_trades']
|
'max_open_trades': max_open_trades,
|
||||||
if config['max_open_trades'] != float('inf') else -1),
|
'max_open_trades_setting': (config['max_open_trades']
|
||||||
|
if config['max_open_trades'] != float('inf') else -1),
|
||||||
'timeframe': config['timeframe'],
|
'timeframe': config['timeframe'],
|
||||||
'timerange': config.get('timerange', ''),
|
'timerange': config.get('timerange', ''),
|
||||||
'enable_protections': config.get('enable_protections', False),
|
'enable_protections': config.get('enable_protections', False),
|
||||||
@ -314,7 +315,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
max_drawdown, drawdown_start, drawdown_end = calculate_max_drawdown(
|
max_drawdown, drawdown_start, drawdown_end = calculate_max_drawdown(
|
||||||
results, value_col='profit_percent')
|
results, value_col='profit_ratio')
|
||||||
strat_stats.update({
|
strat_stats.update({
|
||||||
'max_drawdown': max_drawdown,
|
'max_drawdown': max_drawdown,
|
||||||
'drawdown_start': drawdown_start,
|
'drawdown_start': drawdown_start,
|
||||||
@ -392,7 +393,7 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
|
|||||||
Generate summary table per strategy
|
Generate summary table per strategy
|
||||||
:param stake_currency: stake-currency - used to correctly name headers
|
:param stake_currency: stake-currency - used to correctly name headers
|
||||||
:param max_open_trades: Maximum allowed open trades used for backtest
|
:param max_open_trades: Maximum allowed open trades used for backtest
|
||||||
:param all_results: Dict of <Strategyname: BacktestResult> containing results for all strategies
|
:param all_results: Dict of <Strategyname: DataFrame> containing results for all strategies
|
||||||
:return: pretty printed table with tabulate as string
|
:return: pretty printed table with tabulate as string
|
||||||
"""
|
"""
|
||||||
floatfmt = _get_line_floatfmt()
|
floatfmt = _get_line_floatfmt()
|
||||||
@ -409,8 +410,8 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
|
|||||||
|
|
||||||
def text_table_add_metrics(strat_results: Dict) -> str:
|
def text_table_add_metrics(strat_results: Dict) -> str:
|
||||||
if len(strat_results['trades']) > 0:
|
if len(strat_results['trades']) > 0:
|
||||||
best_trade = max(strat_results['trades'], key=lambda x: x['profit_percent'])
|
best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio'])
|
||||||
worst_trade = min(strat_results['trades'], key=lambda x: x['profit_percent'])
|
worst_trade = min(strat_results['trades'], key=lambda x: x['profit_ratio'])
|
||||||
metrics = [
|
metrics = [
|
||||||
('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)),
|
('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)),
|
||||||
('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)),
|
('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)),
|
||||||
@ -424,9 +425,9 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
|||||||
f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"),
|
f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"),
|
||||||
('Worst Pair', f"{strat_results['worst_pair']['key']} "
|
('Worst Pair', f"{strat_results['worst_pair']['key']} "
|
||||||
f"{round(strat_results['worst_pair']['profit_sum_pct'], 2)}%"),
|
f"{round(strat_results['worst_pair']['profit_sum_pct'], 2)}%"),
|
||||||
('Best trade', f"{best_trade['pair']} {round(best_trade['profit_percent'] * 100, 2)}%"),
|
('Best trade', f"{best_trade['pair']} {round(best_trade['profit_ratio'] * 100, 2)}%"),
|
||||||
('Worst trade', f"{worst_trade['pair']} "
|
('Worst trade', f"{worst_trade['pair']} "
|
||||||
f"{round(worst_trade['profit_percent'] * 100, 2)}%"),
|
f"{round(worst_trade['profit_ratio'] * 100, 2)}%"),
|
||||||
|
|
||||||
('Best day', f"{round(strat_results['backtest_best_day'] * 100, 2)}%"),
|
('Best day', f"{round(strat_results['backtest_best_day'] * 100, 2)}%"),
|
||||||
('Worst day', f"{round(strat_results['backtest_worst_day'] * 100, 2)}%"),
|
('Worst day', f"{round(strat_results['backtest_worst_day'] * 100, 2)}%"),
|
||||||
|
@ -302,6 +302,11 @@ class Trade(_DECL_BASE):
|
|||||||
'close_profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
|
'close_profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
|
||||||
'close_profit_abs': self.close_profit_abs, # Deprecated
|
'close_profit_abs': self.close_profit_abs, # Deprecated
|
||||||
|
|
||||||
|
'trade_duration_s': (int((self.close_date - self.open_date).total_seconds())
|
||||||
|
if self.close_date else None),
|
||||||
|
'trade_duration': (int((self.close_date - self.open_date).total_seconds() // 60)
|
||||||
|
if self.close_date else None),
|
||||||
|
|
||||||
'profit_ratio': self.close_profit,
|
'profit_ratio': self.close_profit,
|
||||||
'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
|
'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
|
||||||
'profit_abs': self.close_profit_abs,
|
'profit_abs': self.close_profit_abs,
|
||||||
|
@ -175,7 +175,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
|
|||||||
# Trades can be empty
|
# Trades can be empty
|
||||||
if trades is not None and len(trades) > 0:
|
if trades is not None and len(trades) > 0:
|
||||||
# Create description for sell summarizing the trade
|
# Create description for sell summarizing the trade
|
||||||
trades['desc'] = trades.apply(lambda row: f"{round(row['profit_percent'] * 100, 1)}%, "
|
trades['desc'] = trades.apply(lambda row: f"{round(row['profit_ratio'] * 100, 1)}%, "
|
||||||
f"{row['sell_reason']}, "
|
f"{row['sell_reason']}, "
|
||||||
f"{row['trade_duration']} min",
|
f"{row['trade_duration']} min",
|
||||||
axis=1)
|
axis=1)
|
||||||
@ -195,9 +195,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
|
|||||||
)
|
)
|
||||||
|
|
||||||
trade_sells = go.Scatter(
|
trade_sells = go.Scatter(
|
||||||
x=trades.loc[trades['profit_percent'] > 0, "close_date"],
|
x=trades.loc[trades['profit_ratio'] > 0, "close_date"],
|
||||||
y=trades.loc[trades['profit_percent'] > 0, "close_rate"],
|
y=trades.loc[trades['profit_ratio'] > 0, "close_rate"],
|
||||||
text=trades.loc[trades['profit_percent'] > 0, "desc"],
|
text=trades.loc[trades['profit_ratio'] > 0, "desc"],
|
||||||
mode='markers',
|
mode='markers',
|
||||||
name='Sell - Profit',
|
name='Sell - Profit',
|
||||||
marker=dict(
|
marker=dict(
|
||||||
@ -208,9 +208,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
trade_sells_loss = go.Scatter(
|
trade_sells_loss = go.Scatter(
|
||||||
x=trades.loc[trades['profit_percent'] <= 0, "close_date"],
|
x=trades.loc[trades['profit_ratio'] <= 0, "close_date"],
|
||||||
y=trades.loc[trades['profit_percent'] <= 0, "close_rate"],
|
y=trades.loc[trades['profit_ratio'] <= 0, "close_rate"],
|
||||||
text=trades.loc[trades['profit_percent'] <= 0, "desc"],
|
text=trades.loc[trades['profit_ratio'] <= 0, "desc"],
|
||||||
mode='markers',
|
mode='markers',
|
||||||
name='Sell - Loss',
|
name='Sell - Loss',
|
||||||
marker=dict(
|
marker=dict(
|
||||||
|
@ -39,8 +39,8 @@ class SampleHyperOptLoss(IHyperOptLoss):
|
|||||||
"""
|
"""
|
||||||
Objective function, returns smaller number for better results
|
Objective function, returns smaller number for better results
|
||||||
"""
|
"""
|
||||||
total_profit = results.profit_percent.sum()
|
total_profit = results['profit_ratio'].sum()
|
||||||
trade_duration = results.trade_duration.mean()
|
trade_duration = results['trade_duration'].mean()
|
||||||
|
|
||||||
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
|
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
|
||||||
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
|
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
|
||||||
|
@ -7,14 +7,13 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime
|
|||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import LAST_BT_RESULT_FN
|
from freqtrade.constants import LAST_BT_RESULT_FN
|
||||||
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism,
|
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD,
|
||||||
calculate_market_change, calculate_max_drawdown,
|
analyze_trade_parallelism, calculate_market_change,
|
||||||
combine_dataframes_with_mean, create_cum_profit,
|
calculate_max_drawdown, combine_dataframes_with_mean,
|
||||||
extract_trades_of_period, get_latest_backtest_filename,
|
create_cum_profit, extract_trades_of_period,
|
||||||
get_latest_hyperopt_file, load_backtest_data, load_trades,
|
get_latest_backtest_filename, get_latest_hyperopt_file,
|
||||||
load_trades_from_db)
|
load_backtest_data, load_trades, load_trades_from_db)
|
||||||
from freqtrade.data.history import load_data, load_pair_history
|
from freqtrade.data.history import load_data, load_pair_history
|
||||||
from freqtrade.optimize.backtesting import BacktestResult
|
|
||||||
from tests.conftest import create_mock_trades
|
from tests.conftest import create_mock_trades
|
||||||
from tests.conftest_trades import MOCK_TRADE_COUNT
|
from tests.conftest_trades import MOCK_TRADE_COUNT
|
||||||
|
|
||||||
@ -55,7 +54,7 @@ def test_load_backtest_data_old_format(testdatadir):
|
|||||||
filename = testdatadir / "backtest-result_test.json"
|
filename = testdatadir / "backtest-result_test.json"
|
||||||
bt_data = load_backtest_data(filename)
|
bt_data = load_backtest_data(filename)
|
||||||
assert isinstance(bt_data, DataFrame)
|
assert isinstance(bt_data, DataFrame)
|
||||||
assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profit_abs"]
|
assert list(bt_data.columns) == BT_DATA_COLUMNS_OLD + ['profit_abs', 'profit_ratio']
|
||||||
assert len(bt_data) == 179
|
assert len(bt_data) == 179
|
||||||
|
|
||||||
# Test loading from string (must yield same result)
|
# Test loading from string (must yield same result)
|
||||||
@ -71,7 +70,7 @@ def test_load_backtest_data_new_format(testdatadir):
|
|||||||
filename = testdatadir / "backtest-result_new.json"
|
filename = testdatadir / "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(list(BacktestResult._fields) + ["profit_abs"])
|
assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID)
|
||||||
assert len(bt_data) == 179
|
assert len(bt_data) == 179
|
||||||
|
|
||||||
# Test loading from string (must yield same result)
|
# Test loading from string (must yield same result)
|
||||||
@ -95,7 +94,7 @@ def test_load_backtest_data_multi(testdatadir):
|
|||||||
for strategy in ('DefaultStrategy', 'TestStrategy'):
|
for strategy in ('DefaultStrategy', '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)
|
||||||
assert set(bt_data.columns) == set(list(BacktestResult._fields) + ["profit_abs"])
|
assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID)
|
||||||
assert len(bt_data) == 179
|
assert len(bt_data) == 179
|
||||||
|
|
||||||
# Test loading from string (must yield same result)
|
# Test loading from string (must yield same result)
|
||||||
@ -122,7 +121,7 @@ def test_load_trades_from_db(default_conf, fee, mocker):
|
|||||||
assert isinstance(trades, DataFrame)
|
assert isinstance(trades, DataFrame)
|
||||||
assert "pair" in trades.columns
|
assert "pair" in trades.columns
|
||||||
assert "open_date" in trades.columns
|
assert "open_date" in trades.columns
|
||||||
assert "profit_percent" in trades.columns
|
assert "profit_ratio" in trades.columns
|
||||||
|
|
||||||
for col in BT_DATA_COLUMNS:
|
for col in BT_DATA_COLUMNS:
|
||||||
if col not in ['index', 'open_at_end']:
|
if col not in ['index', 'open_at_end']:
|
||||||
|
@ -37,7 +37,7 @@ def hyperopt_results():
|
|||||||
return pd.DataFrame(
|
return pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [-0.1, 0.2, 0.3],
|
'profit_ratio': [-0.1, 0.2, 0.3],
|
||||||
'profit_abs': [-0.2, 0.4, 0.6],
|
'profit_abs': [-0.2, 0.4, 0.6],
|
||||||
'trade_duration': [10, 30, 10],
|
'trade_duration': [10, 30, 10],
|
||||||
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.ROI],
|
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.ROI],
|
||||||
|
@ -510,7 +510,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert len(results) == len(data.trades)
|
assert len(results) == len(data.trades)
|
||||||
assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3)
|
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
|
||||||
|
|
||||||
for c, trade in enumerate(data.trades):
|
for c, trade in enumerate(data.trades):
|
||||||
res = results.iloc[c]
|
res = results.iloc[c]
|
||||||
|
@ -445,7 +445,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
|||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
|
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||||
default_conf['ask_strategy']['use_sell_signal'] = False
|
default_conf['ask_strategy']['use_sell_signal'] = False
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
@ -469,21 +469,28 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
|
|||||||
|
|
||||||
expected = pd.DataFrame(
|
expected = pd.DataFrame(
|
||||||
{'pair': [pair, pair],
|
{'pair': [pair, pair],
|
||||||
'profit_percent': [0.0, 0.0],
|
'stake_amount': [0.001, 0.001],
|
||||||
'profit_abs': [0.0, 0.0],
|
'amount': [0.00957442, 0.0097064],
|
||||||
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
|
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
|
||||||
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
|
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
|
||||||
),
|
),
|
||||||
'open_rate': [0.104445, 0.10302485],
|
|
||||||
'open_fee': [0.0025, 0.0025],
|
|
||||||
'close_date': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime,
|
'close_date': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime,
|
||||||
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True),
|
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True),
|
||||||
|
'open_rate': [0.104445, 0.10302485],
|
||||||
'close_rate': [0.104969, 0.103541],
|
'close_rate': [0.104969, 0.103541],
|
||||||
'close_fee': [0.0025, 0.0025],
|
'fee_open': [0.0025, 0.0025],
|
||||||
'amount': [0.00957442, 0.0097064],
|
'fee_close': [0.0025, 0.0025],
|
||||||
'trade_duration': [235, 40],
|
'trade_duration': [235, 40],
|
||||||
'open_at_end': [False, False],
|
'profit_ratio': [0.0, 0.0],
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI]
|
'profit_abs': [0.0, 0.0],
|
||||||
|
'sell_reason': [SellType.ROI, SellType.ROI],
|
||||||
|
'initial_stop_loss_abs': [0.0940005, 0.09272236],
|
||||||
|
'initial_stop_loss_ratio': [-0.1, -0.1],
|
||||||
|
'stop_loss_abs': [0.0940005, 0.09272236],
|
||||||
|
'stop_loss_ratio': [-0.1, -0.1],
|
||||||
|
'min_rate': [0.1038, 0.10302485],
|
||||||
|
'max_rate': [0.10501, 0.1038888],
|
||||||
|
'is_open': [False, False],
|
||||||
})
|
})
|
||||||
pd.testing.assert_frame_equal(results, expected)
|
pd.testing.assert_frame_equal(results, expected)
|
||||||
data_pair = processed[pair]
|
data_pair = processed[pair]
|
||||||
@ -629,7 +636,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
|||||||
# 100 buys signals
|
# 100 buys signals
|
||||||
assert len(results) == 100
|
assert len(results) == 100
|
||||||
# One trade was force-closed at the end
|
# One trade was force-closed at the end
|
||||||
assert len(results.loc[results.open_at_end]) == 0
|
assert len(results.loc[results['is_open']]) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC'])
|
@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC'])
|
||||||
@ -737,7 +744,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
|||||||
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||||
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS + ['profit_abs']))
|
backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS))
|
||||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
||||||
@ -803,7 +810,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtestmock = MagicMock(side_effect=[
|
backtestmock = MagicMock(side_effect=[
|
||||||
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
||||||
'profit_percent': [0.0, 0.0],
|
'profit_ratio': [0.0, 0.0],
|
||||||
'profit_abs': [0.0, 0.0],
|
'profit_abs': [0.0, 0.0],
|
||||||
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||||
'2018-01-30 03:30:00', ], utc=True
|
'2018-01-30 03:30:00', ], utc=True
|
||||||
@ -811,13 +818,13 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||||
'2018-01-30 05:35:00', ], utc=True),
|
'2018-01-30 05:35:00', ], utc=True),
|
||||||
'trade_duration': [235, 40],
|
'trade_duration': [235, 40],
|
||||||
'open_at_end': [False, False],
|
'is_open': [False, False],
|
||||||
'open_rate': [0.104445, 0.10302485],
|
'open_rate': [0.104445, 0.10302485],
|
||||||
'close_rate': [0.104969, 0.103541],
|
'close_rate': [0.104969, 0.103541],
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI]
|
'sell_reason': [SellType.ROI, SellType.ROI]
|
||||||
}),
|
}),
|
||||||
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.03, 0.01, 0.1],
|
'profit_ratio': [0.03, 0.01, 0.1],
|
||||||
'profit_abs': [0.01, 0.02, 0.2],
|
'profit_abs': [0.01, 0.02, 0.2],
|
||||||
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||||
'2018-01-30 03:30:00',
|
'2018-01-30 03:30:00',
|
||||||
@ -827,7 +834,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
'2018-01-30 05:35:00',
|
'2018-01-30 05:35:00',
|
||||||
'2018-01-30 08:30:00'], utc=True),
|
'2018-01-30 08:30:00'], utc=True),
|
||||||
'trade_duration': [47, 40, 20],
|
'trade_duration': [47, 40, 20],
|
||||||
'open_at_end': [False, False, False],
|
'is_open': [False, False, False],
|
||||||
'open_rate': [0.104445, 0.10302485, 0.122541],
|
'open_rate': [0.104445, 0.10302485, 0.122541],
|
||||||
'close_rate': [0.104969, 0.103541, 0.123541],
|
'close_rate': [0.104969, 0.103541, 0.123541],
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||||
|
@ -427,7 +427,7 @@ def test_format_results(hyperopt):
|
|||||||
('LTC/BTC', 1, 1, 123),
|
('LTC/BTC', 1, 1, 123),
|
||||||
('XPR/BTC', -1, -2, -246)
|
('XPR/BTC', -1, -2, -246)
|
||||||
]
|
]
|
||||||
labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration']
|
labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
|
||||||
df = pd.DataFrame.from_records(trades, columns=labels)
|
df = pd.DataFrame.from_records(trades, columns=labels)
|
||||||
results_metrics = hyperopt._calculate_results_metrics(df)
|
results_metrics = hyperopt._calculate_results_metrics(df)
|
||||||
results_explanation = hyperopt._format_results_explanation_string(results_metrics)
|
results_explanation = hyperopt._format_results_explanation_string(results_metrics)
|
||||||
@ -567,7 +567,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
|||||||
trades = [
|
trades = [
|
||||||
('TRX/BTC', 0.023117, 0.000233, 100)
|
('TRX/BTC', 0.023117, 0.000233, 100)
|
||||||
]
|
]
|
||||||
labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration']
|
labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
|
||||||
backtest_result = pd.DataFrame.from_records(trades, columns=labels)
|
backtest_result = pd.DataFrame.from_records(trades, columns=labels)
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
|
@ -60,9 +60,9 @@ def test_loss_calculation_prefer_shorter_trades(hyperopt_conf, hyperopt_results)
|
|||||||
|
|
||||||
def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> None:
|
def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> None:
|
||||||
results_over = hyperopt_results.copy()
|
results_over = hyperopt_results.copy()
|
||||||
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
|
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
|
||||||
results_under = hyperopt_results.copy()
|
results_under = hyperopt_results.copy()
|
||||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
|
||||||
|
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
|
||||||
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
|
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
|
||||||
@ -77,9 +77,9 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
|
|||||||
|
|
||||||
def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
||||||
results_over = hyperopt_results.copy()
|
results_over = hyperopt_results.copy()
|
||||||
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
|
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
|
||||||
results_under = hyperopt_results.copy()
|
results_under = hyperopt_results.copy()
|
||||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
|
||||||
|
|
||||||
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLoss'})
|
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLoss'})
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||||
@ -95,9 +95,9 @@ def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> N
|
|||||||
|
|
||||||
def test_sharpe_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
def test_sharpe_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
||||||
results_over = hyperopt_results.copy()
|
results_over = hyperopt_results.copy()
|
||||||
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
|
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
|
||||||
results_under = hyperopt_results.copy()
|
results_under = hyperopt_results.copy()
|
||||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
|
||||||
|
|
||||||
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'})
|
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'})
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||||
@ -113,9 +113,9 @@ def test_sharpe_loss_daily_prefers_higher_profits(default_conf, hyperopt_results
|
|||||||
|
|
||||||
def test_sortino_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
def test_sortino_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
||||||
results_over = hyperopt_results.copy()
|
results_over = hyperopt_results.copy()
|
||||||
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
|
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
|
||||||
results_under = hyperopt_results.copy()
|
results_under = hyperopt_results.copy()
|
||||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
|
||||||
|
|
||||||
default_conf.update({'hyperopt_loss': 'SortinoHyperOptLoss'})
|
default_conf.update({'hyperopt_loss': 'SortinoHyperOptLoss'})
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||||
@ -131,9 +131,9 @@ def test_sortino_loss_prefers_higher_profits(default_conf, hyperopt_results) ->
|
|||||||
|
|
||||||
def test_sortino_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
def test_sortino_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
||||||
results_over = hyperopt_results.copy()
|
results_over = hyperopt_results.copy()
|
||||||
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
|
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
|
||||||
results_under = hyperopt_results.copy()
|
results_under = hyperopt_results.copy()
|
||||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
|
||||||
|
|
||||||
default_conf.update({'hyperopt_loss': 'SortinoHyperOptLossDaily'})
|
default_conf.update({'hyperopt_loss': 'SortinoHyperOptLossDaily'})
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||||
@ -149,9 +149,9 @@ def test_sortino_loss_daily_prefers_higher_profits(default_conf, hyperopt_result
|
|||||||
|
|
||||||
def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
||||||
results_over = hyperopt_results.copy()
|
results_over = hyperopt_results.copy()
|
||||||
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
|
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
|
||||||
results_under = hyperopt_results.copy()
|
results_under = hyperopt_results.copy()
|
||||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
|
||||||
|
|
||||||
default_conf.update({'hyperopt_loss': 'OnlyProfitHyperOptLoss'})
|
default_conf.update({'hyperopt_loss': 'OnlyProfitHyperOptLoss'})
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||||
|
@ -27,7 +27,7 @@ def test_text_table_bt_results():
|
|||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['ETH/BTC', 'ETH/BTC'],
|
'pair': ['ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.1, 0.2],
|
'profit_ratio': [0.1, 0.2],
|
||||||
'profit_abs': [0.2, 0.4],
|
'profit_abs': [0.2, 0.4],
|
||||||
'trade_duration': [10, 30],
|
'trade_duration': [10, 30],
|
||||||
'wins': [2, 0],
|
'wins': [2, 0],
|
||||||
@ -59,7 +59,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
|||||||
results = {'DefStrat': {
|
results = {'DefStrat': {
|
||||||
'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
|
'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
|
||||||
"UNITTEST/BTC", "UNITTEST/BTC"],
|
"UNITTEST/BTC", "UNITTEST/BTC"],
|
||||||
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
|
"profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
|
||||||
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
|
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
|
||||||
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
|
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
|
||||||
Arrow(2017, 11, 14, 21, 36, 00).datetime,
|
Arrow(2017, 11, 14, 21, 36, 00).datetime,
|
||||||
@ -72,7 +72,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
|||||||
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
|
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
|
||||||
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
|
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
|
||||||
"trade_duration": [123, 34, 31, 14],
|
"trade_duration": [123, 34, 31, 14],
|
||||||
"open_at_end": [False, False, False, True],
|
"is_open": [False, False, False, True],
|
||||||
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
|
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
|
||||||
SellType.ROI, SellType.FORCE_SELL]
|
SellType.ROI, SellType.FORCE_SELL]
|
||||||
}),
|
}),
|
||||||
@ -103,7 +103,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
|||||||
results = {'DefStrat': {
|
results = {'DefStrat': {
|
||||||
'results': pd.DataFrame(
|
'results': pd.DataFrame(
|
||||||
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
|
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
|
||||||
"profit_percent": [0.003312, 0.010801, -0.013803, 0.002780],
|
"profit_ratio": [0.003312, 0.010801, -0.013803, 0.002780],
|
||||||
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
|
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
|
||||||
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
|
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
|
||||||
Arrow(2017, 11, 14, 21, 36, 00).datetime,
|
Arrow(2017, 11, 14, 21, 36, 00).datetime,
|
||||||
@ -179,7 +179,7 @@ def test_generate_pair_metrics():
|
|||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['ETH/BTC', 'ETH/BTC'],
|
'pair': ['ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.1, 0.2],
|
'profit_ratio': [0.1, 0.2],
|
||||||
'profit_abs': [0.2, 0.4],
|
'profit_abs': [0.2, 0.4],
|
||||||
'trade_duration': [10, 30],
|
'trade_duration': [10, 30],
|
||||||
'wins': [2, 0],
|
'wins': [2, 0],
|
||||||
@ -227,7 +227,7 @@ def test_text_table_sell_reason():
|
|||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.1, 0.2, -0.1],
|
'profit_ratio': [0.1, 0.2, -0.1],
|
||||||
'profit_abs': [0.2, 0.4, -0.2],
|
'profit_abs': [0.2, 0.4, -0.2],
|
||||||
'trade_duration': [10, 30, 10],
|
'trade_duration': [10, 30, 10],
|
||||||
'wins': [2, 0, 0],
|
'wins': [2, 0, 0],
|
||||||
@ -259,7 +259,7 @@ def test_generate_sell_reason_stats():
|
|||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.1, 0.2, -0.1],
|
'profit_ratio': [0.1, 0.2, -0.1],
|
||||||
'profit_abs': [0.2, 0.4, -0.2],
|
'profit_abs': [0.2, 0.4, -0.2],
|
||||||
'trade_duration': [10, 30, 10],
|
'trade_duration': [10, 30, 10],
|
||||||
'wins': [2, 0, 0],
|
'wins': [2, 0, 0],
|
||||||
@ -295,7 +295,7 @@ def test_text_table_strategy(default_conf):
|
|||||||
results['TestStrategy1'] = {'results': pd.DataFrame(
|
results['TestStrategy1'] = {'results': pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.1, 0.2, 0.3],
|
'profit_ratio': [0.1, 0.2, 0.3],
|
||||||
'profit_abs': [0.2, 0.4, 0.5],
|
'profit_abs': [0.2, 0.4, 0.5],
|
||||||
'trade_duration': [10, 30, 10],
|
'trade_duration': [10, 30, 10],
|
||||||
'wins': [2, 0, 0],
|
'wins': [2, 0, 0],
|
||||||
@ -307,7 +307,7 @@ def test_text_table_strategy(default_conf):
|
|||||||
results['TestStrategy2'] = {'results': pd.DataFrame(
|
results['TestStrategy2'] = {'results': pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
|
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
|
||||||
'profit_percent': [0.4, 0.2, 0.3],
|
'profit_ratio': [0.4, 0.2, 0.3],
|
||||||
'profit_abs': [0.4, 0.4, 0.5],
|
'profit_abs': [0.4, 0.4, 0.5],
|
||||||
'trade_duration': [15, 30, 15],
|
'trade_duration': [15, 30, 15],
|
||||||
'wins': [4, 1, 0],
|
'wins': [4, 1, 0],
|
||||||
|
@ -80,6 +80,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'amount': 91.07468123,
|
'amount': 91.07468123,
|
||||||
'amount_requested': 91.07468123,
|
'amount_requested': 91.07468123,
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
|
'trade_duration': None,
|
||||||
|
'trade_duration_s': None,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'close_profit_pct': None,
|
'close_profit_pct': None,
|
||||||
'close_profit_abs': None,
|
'close_profit_abs': None,
|
||||||
@ -144,6 +146,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'current_rate': ANY,
|
'current_rate': ANY,
|
||||||
'amount': 91.07468123,
|
'amount': 91.07468123,
|
||||||
'amount_requested': 91.07468123,
|
'amount_requested': 91.07468123,
|
||||||
|
'trade_duration': ANY,
|
||||||
|
'trade_duration_s': ANY,
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'close_profit_pct': None,
|
'close_profit_pct': None,
|
||||||
|
@ -815,6 +815,8 @@ def test_to_json(default_conf, fee):
|
|||||||
'amount': 123.0,
|
'amount': 123.0,
|
||||||
'amount_requested': 123.0,
|
'amount_requested': 123.0,
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
|
'trade_duration': None,
|
||||||
|
'trade_duration_s': None,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'close_profit_pct': None,
|
'close_profit_pct': None,
|
||||||
'close_profit_abs': None,
|
'close_profit_abs': None,
|
||||||
@ -869,6 +871,8 @@ def test_to_json(default_conf, fee):
|
|||||||
'amount': 100.0,
|
'amount': 100.0,
|
||||||
'amount_requested': 101.0,
|
'amount_requested': 101.0,
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
|
'trade_duration': 60,
|
||||||
|
'trade_duration_s': 3600,
|
||||||
'stop_loss_abs': None,
|
'stop_loss_abs': None,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
'stop_loss_ratio': None,
|
'stop_loss_ratio': None,
|
||||||
|
File diff suppressed because one or more lines are too long
2
tests/testdata/backtest-result_new.json
vendored
2
tests/testdata/backtest-result_new.json
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user