Merge pull request #4268 from freqtrade/backtest_trade_object

Backtest trade object
This commit is contained in:
Matthias
2021-01-27 19:10:21 +01:00
committed by GitHub
26 changed files with 181 additions and 202 deletions

View File

@@ -2,9 +2,8 @@
Helpers when analyzing backtest data
"""
import logging
from datetime import timezone
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 pandas as pd
@@ -16,9 +15,22 @@ from freqtrade.persistence import Trade, init_db
logger = logging.getLogger(__name__)
# must align with columns in backtest.py
BT_DATA_COLUMNS = ["pair", "profit_percent", "open_date", "close_date", "index", "trade_duration",
"open_rate", "close_rate", "open_at_end", "sell_reason"]
# Old format - maybe remove?
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
"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:
@@ -154,7 +166,7 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
)
else:
# 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'],
unit='s',
@@ -166,7 +178,10 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
utc=True,
infer_datetime_format=True
)
# Create compatibility with new format
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)
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]
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:
"""
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)
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 = []
if strategy:
filters.append(Trade.strategy == strategy)
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)
trades = trade_list_to_dataframe(Trade.get_trades(filters).all())
return trades

View File

@@ -7,13 +7,14 @@ import logging
from collections import defaultdict
from copy import deepcopy
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 freqtrade.configuration import TimeRange, remove_credentials, validate_config_consistency
from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.data import history
from freqtrade.data.btanalysis import trade_list_to_dataframe
from freqtrade.data.converter import trim_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.exceptions import OperationalException
@@ -41,25 +42,6 @@ LOW_IDX = 5
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:
"""
Backtesting class, this class contains all the logic to run a backtest
@@ -264,7 +246,7 @@ class Backtesting:
else:
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_row[BUY_IDX], sell_row[SELL_IDX],
@@ -276,25 +258,12 @@ class Backtesting:
trade.close_date = sell_row[DATE_IDX]
trade.sell_reason = sell.sell_type
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
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
"""
@@ -304,24 +273,11 @@ class Backtesting:
for trade in open_trades[pair]:
sell_row = data[pair][-1]
trade_entry = BacktestResult(pair=trade.pair,
profit_percent=trade.calc_profit_ratio(
rate=sell_row[OPEN_IDX]),
profit_abs=trade.calc_profit(sell_row[OPEN_IDX]),
open_date=trade.open_date,
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)
trade.close_date = sell_row[DATE_IDX]
trade.sell_reason = SellType.FORCE_SELL
trade.close(sell_row[OPEN_IDX], show_msg=False)
trade.is_open = True
trades.append(trade)
return trades
def backtest(self, processed: Dict, stake_amount: float,
@@ -348,7 +304,7 @@ class Backtesting:
f"start_date: {start_date}, end_date: {end_date}, "
f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}"
)
trades = []
trades: List[Trade] = []
self.prepare_backtest(enable_protections)
# Use dict of lists with data for performance
@@ -429,7 +385,7 @@ class Backtesting:
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):
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())

View File

@@ -42,7 +42,7 @@ class ShortTradeDurHyperOptLoss(IHyperOptLoss):
* 0.25: Avoiding trade loss
* 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_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)

View File

@@ -574,20 +574,20 @@ class Hyperopt:
}
def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict:
wins = len(backtesting_results[backtesting_results.profit_percent > 0])
draws = len(backtesting_results[backtesting_results.profit_percent == 0])
losses = len(backtesting_results[backtesting_results.profit_percent < 0])
wins = len(backtesting_results[backtesting_results['profit_ratio'] > 0])
draws = len(backtesting_results[backtesting_results['profit_ratio'] == 0])
losses = len(backtesting_results[backtesting_results['profit_ratio'] < 0])
return {
'trade_count': len(backtesting_results.index),
'wins': wins,
'draws': draws,
'losses': losses,
'winsdrawslosses': f"{wins:>4} {draws:>4} {losses:>4}",
'avg_profit': backtesting_results.profit_percent.mean() * 100.0,
'median_profit': backtesting_results.profit_percent.median() * 100.0,
'total_profit': backtesting_results.profit_abs.sum(),
'profit': backtesting_results.profit_percent.sum() * 100.0,
'duration': backtesting_results.trade_duration.mean(),
'avg_profit': backtesting_results['profit_ratio'].mean() * 100.0,
'median_profit': backtesting_results['profit_ratio'].median() * 100.0,
'total_profit': backtesting_results['profit_abs'].sum(),
'profit': backtesting_results['profit_ratio'].sum() * 100.0,
'duration': backtesting_results['trade_duration'].mean(),
}
def _format_results_explanation_string(self, results_metrics: Dict) -> str:

View File

@@ -34,5 +34,5 @@ class OnlyProfitHyperOptLoss(IHyperOptLoss):
"""
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

View File

@@ -28,7 +28,7 @@ class SharpeHyperOptLoss(IHyperOptLoss):
Uses Sharpe Ratio calculation.
"""
total_profit = results["profit_percent"]
total_profit = results["profit_ratio"]
days_period = (max_date - min_date).days
# adding slippage of 0.1% per trade

View File

@@ -34,9 +34,9 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
annual_risk_free_rate = 0.0
risk_free_rate = annual_risk_free_rate / days_in_year
# apply slippage per trade to profit_percent
results.loc[:, 'profit_percent_after_slippage'] = \
results['profit_percent'] - slippage_per_trade_ratio
# apply slippage per trade to profit_ratio
results.loc[:, 'profit_ratio_after_slippage'] = \
results['profit_ratio'] - slippage_per_trade_ratio
# create the index within the min_date and end max_date
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
@@ -44,10 +44,10 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
sum_daily = (
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()
up_stdev = total_profit.std()

View File

@@ -28,7 +28,7 @@ class SortinoHyperOptLoss(IHyperOptLoss):
Uses Sortino Ratio calculation.
"""
total_profit = results["profit_percent"]
total_profit = results["profit_ratio"]
days_period = (max_date - min_date).days
# adding slippage of 0.1% per trade
@@ -36,7 +36,7 @@ class SortinoHyperOptLoss(IHyperOptLoss):
expected_returns_mean = total_profit.sum() / days_period
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'])
if down_stdev != 0:

View File

@@ -36,9 +36,9 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
days_in_year = 365
minimum_acceptable_return = 0.0
# apply slippage per trade to profit_percent
results.loc[:, 'profit_percent_after_slippage'] = \
results['profit_percent'] - slippage_per_trade_ratio
# apply slippage per trade to profit_ratio
results.loc[:, 'profit_ratio_after_slippage'] = \
results['profit_ratio'] - slippage_per_trade_ratio
# create the index within the min_date and end max_date
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
@@ -46,17 +46,17 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
sum_daily = (
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()
sum_daily['downside_returns'] = 0
sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit
total_downside = sum_daily['downside_returns']
# 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))
if down_stdev != 0:

View File

@@ -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.
"""
profit_sum = result['profit_percent'].sum()
profit_sum = result['profit_ratio'].sum()
profit_total = profit_sum / max_open_trades
return {
'key': first_column,
'trades': len(result),
'profit_mean': result['profit_percent'].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': result['profit_ratio'].mean() 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_pct': round(profit_sum * 100.0, 2),
'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():
result = results.loc[results['sell_reason'] == reason]
profit_mean = result['profit_percent'].mean()
profit_sum = result['profit_percent'].sum()
profit_mean = result['profit_ratio'].mean()
profit_sum = result['profit_ratio'].sum()
profit_total = profit_sum / max_open_trades
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]:
"""
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
"""
@@ -199,15 +199,15 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]:
'winner_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)
best = max(daily_profit)
winning_days = sum(daily_profit > 0)
draw_days = sum(daily_profit == 0)
losing_days = sum(daily_profit < 0)
winning_trades = results.loc[results['profit_percent'] > 0]
losing_trades = results.loc[results['profit_percent'] < 0]
winning_trades = results.loc[results['profit_ratio'] > 0]
losing_trades = results.loc[results['profit_ratio'] < 0]
return {
'backtest_best_day': best,
@@ -243,7 +243,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
if not isinstance(results, DataFrame):
continue
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']
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
@@ -253,7 +253,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
results=results)
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
max_open_trades=max_open_trades,
results=results.loc[results['open_at_end']],
results=results.loc[results['is_open']],
skip_nan=True)
daily_stats = generate_daily_stats(results)
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,
'left_open_trades': left_open_results,
'total_trades': len(results),
'profit_mean': results['profit_percent'].mean() if len(results) > 0 else 0,
'profit_total': results['profit_percent'].sum(),
'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0,
'profit_total': results['profit_ratio'].sum() / max_open_trades,
'profit_total_abs': results['profit_abs'].sum(),
'backtest_start': min_date.datetime,
'backtest_start_ts': min_date.int_timestamp * 1000,
@@ -290,8 +290,9 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
'pairlist': list(btdata.keys()),
'stake_amount': config['stake_amount'],
'stake_currency': config['stake_currency'],
'max_open_trades': (config['max_open_trades']
if config['max_open_trades'] != float('inf') else -1),
'max_open_trades': max_open_trades,
'max_open_trades_setting': (config['max_open_trades']
if config['max_open_trades'] != float('inf') else -1),
'timeframe': config['timeframe'],
'timerange': config.get('timerange', ''),
'enable_protections': config.get('enable_protections', False),
@@ -314,7 +315,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
try:
max_drawdown, drawdown_start, drawdown_end = calculate_max_drawdown(
results, value_col='profit_percent')
results, value_col='profit_ratio')
strat_stats.update({
'max_drawdown': max_drawdown,
'drawdown_start': drawdown_start,
@@ -392,7 +393,7 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
Generate summary table per strategy
:param stake_currency: stake-currency - used to correctly name headers
: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
"""
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:
if len(strat_results['trades']) > 0:
best_trade = max(strat_results['trades'], key=lambda x: x['profit_percent'])
worst_trade = min(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_ratio'])
metrics = [
('Backtesting from', strat_results['backtest_start'].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)}%"),
('Worst Pair', f"{strat_results['worst_pair']['key']} "
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']} "
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)}%"),
('Worst day', f"{round(strat_results['backtest_worst_day'] * 100, 2)}%"),

View File

@@ -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_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_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
'profit_abs': self.close_profit_abs,

View File

@@ -175,7 +175,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
# Trades can be empty
if trades is not None and len(trades) > 0:
# 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['trade_duration']} min",
axis=1)
@@ -195,9 +195,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
)
trade_sells = go.Scatter(
x=trades.loc[trades['profit_percent'] > 0, "close_date"],
y=trades.loc[trades['profit_percent'] > 0, "close_rate"],
text=trades.loc[trades['profit_percent'] > 0, "desc"],
x=trades.loc[trades['profit_ratio'] > 0, "close_date"],
y=trades.loc[trades['profit_ratio'] > 0, "close_rate"],
text=trades.loc[trades['profit_ratio'] > 0, "desc"],
mode='markers',
name='Sell - Profit',
marker=dict(
@@ -208,9 +208,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
)
)
trade_sells_loss = go.Scatter(
x=trades.loc[trades['profit_percent'] <= 0, "close_date"],
y=trades.loc[trades['profit_percent'] <= 0, "close_rate"],
text=trades.loc[trades['profit_percent'] <= 0, "desc"],
x=trades.loc[trades['profit_ratio'] <= 0, "close_date"],
y=trades.loc[trades['profit_ratio'] <= 0, "close_rate"],
text=trades.loc[trades['profit_ratio'] <= 0, "desc"],
mode='markers',
name='Sell - Loss',
marker=dict(

View File

@@ -39,8 +39,8 @@ class SampleHyperOptLoss(IHyperOptLoss):
"""
Objective function, returns smaller number for better results
"""
total_profit = results.profit_percent.sum()
trade_duration = results.trade_duration.mean()
total_profit = results['profit_ratio'].sum()
trade_duration = results['trade_duration'].mean()
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)