Don't use profit_percent for backtesting results anymore
This commit is contained in:
parent
48977493bb
commit
8ee264bc59
@ -3,11 +3,12 @@
|
|||||||
"""
|
"""
|
||||||
This module contains the backtesting logic
|
This module contains the backtesting logic
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.data.btanalysis import BT_DATA_COLUMNS
|
||||||
import logging
|
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, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
@ -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
|
||||||
@ -403,12 +385,7 @@ class Backtesting:
|
|||||||
|
|
||||||
trades += self.handle_left_open(open_trades, data=data)
|
trades += self.handle_left_open(open_trades, data=data)
|
||||||
|
|
||||||
cols = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
|
df = DataFrame.from_records([t.to_json() for t in trades], columns=BT_DATA_COLUMNS)
|
||||||
'open_fee', 'close_fee', 'trade_duration',
|
|
||||||
'profit_ratio', 'profit_percent', 'profit_abs', 'sell_reason',
|
|
||||||
'initial_stop_loss_abs', 'initial_stop_loss_ratio' 'stop_loss', 'stop_loss_ratio',
|
|
||||||
'min_rate', 'max_rate', 'is_open', ]
|
|
||||||
df = DataFrame.from_records([t.to_json() for t in trades], columns=cols)
|
|
||||||
if len(df) > 0:
|
if len(df) > 0:
|
||||||
df.loc[:, 'close_date'] = to_datetime(df['close_date'], utc=True)
|
df.loc[:, 'close_date'] = to_datetime(df['close_date'], utc=True)
|
||||||
df.loc[:, 'open_date'] = to_datetime(df['open_date'], utc=True)
|
df.loc[:, 'open_date'] = to_datetime(df['open_date'], utc=True)
|
||||||
|
@ -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,19 +574,19 @@ 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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
@ -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(),
|
||||||
'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,
|
||||||
@ -314,7 +314,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 +392,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 +409,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 +424,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)}%"),
|
||||||
|
@ -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)
|
||||||
|
@ -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]
|
||||||
|
@ -469,7 +469,7 @@ 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],
|
'profit_ratio': [0.0, 0.0],
|
||||||
'profit_abs': [0.0, 0.0],
|
'profit_abs': [0.0, 0.0],
|
||||||
'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
|
||||||
@ -803,7 +803,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
|
||||||
@ -817,7 +817,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
'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',
|
||||||
|
@ -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,
|
||||||
@ -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],
|
||||||
|
Loading…
Reference in New Issue
Block a user