sell_reason -> exit_reason

This commit is contained in:
Sam Germain 2022-01-04 22:52:52 -06:00
parent c899eabe1d
commit f5805543ed
43 changed files with 324 additions and 324 deletions

View File

@ -56,7 +56,7 @@ Currently, the arguments are:
* `results`: DataFrame containing the resulting trades.
The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`):
`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`
`pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date, close_rate, fee_close, amount, trade_duration, is_open, exit_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs`
* `trade_count`: Amount of trades (identical to `len(results)`)
* `min_date`: Start date of the timerange used
* `min_date`: End date of the timerange used

View File

@ -65,7 +65,7 @@ SET is_open=0,
close_rate=<close_rate>,
close_profit = close_rate / open_rate - 1,
close_profit_abs = (amount * <close_rate> * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))),
sell_reason=<sell_reason>
exit_reason=<exit_reason>
WHERE id=<trade_ID_to_update>;
```
@ -78,7 +78,7 @@ SET is_open=0,
close_rate=0.19638016,
close_profit=0.0496,
close_profit_abs = (amount * 0.19638016 * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))),
sell_reason='force_sell'
exit_reason='force_sell'
WHERE id=31;
```

View File

@ -49,7 +49,7 @@ from freqtrade.exchange import timeframe_to_prev_date
class AwesomeStrategy(IStrategy):
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str,
rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -> bool:
# Obtain pair dataframe.
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
@ -125,7 +125,7 @@ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame
The provided exit-tag is then used as sell-reason - and shown as such in backtest results.
!!! Note
`sell_reason` is limited to 100 characters, remaining data will be truncated.
`exit_reason` is limited to 100 characters, remaining data will be truncated.
## Strategy version

View File

@ -539,7 +539,7 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str,
rate: float, time_in_force: str, exit_reason: str,
current_time: datetime, **kwargs) -> bool:
"""
Called right before placing a regular sell order.
@ -555,7 +555,7 @@ class AwesomeStrategy(IStrategy):
:param amount: Amount in quote currency.
:param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param sell_reason: Sell reason.
:param exit_reason: Sell reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell']
:param current_time: datetime object, containing the current datetime
@ -563,7 +563,7 @@ class AwesomeStrategy(IStrategy):
:return bool: When True is returned, then the sell-order is placed on the exchange.
False aborts the process
"""
if sell_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0:
if exit_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0:
# Reject force-sells with negative profit
# This is just a sample, please adjust to your needs
# (this does not necessarily make sense, assuming you know when you're force-selling)

View File

@ -822,7 +822,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati
!!! Note
Providing invalid input to `stoploss_from_open()` may produce "CustomStoploss function did not return valid stoploss" warnings.
This may happen if `current_profit` parameter is below specified `open_relative_stop`. Such situations may arise when closing trade
is blocked by `confirm_trade_exit()` method. Warnings can be solved by never blocking stop loss sells by checking `sell_reason` in
is blocked by `confirm_trade_exit()` method. Warnings can be solved by never blocking stop loss sells by checking `exit_reason` in
`confirm_trade_exit()`, or by using `return stoploss_from_open(...) or 1` idiom, which will request to not change stop loss when
`current_profit < open_relative_stop`.

View File

@ -129,7 +129,7 @@ print(stats['strategy_comparison'])
trades = load_backtest_data(backtest_dir)
# Show value-counts per pair
trades.groupby("pair")["sell_reason"].value_counts()
trades.groupby("pair")["exit_reason"].value_counts()
```
## Plotting daily profit / equity line
@ -182,7 +182,7 @@ from freqtrade.data.btanalysis import load_trades_from_db
trades = load_trades_from_db("sqlite:///tradesv3.sqlite")
# Display results
trades.groupby("pair")["sell_reason"].value_counts()
trades.groupby("pair")["exit_reason"].value_counts()
```
## Analyze the loaded trades for trade parallelism

View File

@ -178,7 +178,7 @@ Possible parameters are:
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `sell_reason`
* `exit_reason`
* `order_type`
* `open_date`
* `close_date`
@ -203,7 +203,7 @@ Possible parameters are:
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `sell_reason`
* `exit_reason`
* `order_type`
* `open_date`
* `close_date`
@ -228,7 +228,7 @@ Possible parameters are:
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `sell_reason`
* `exit_reason`
* `order_type`
* `open_date`
* `close_date`

View File

@ -17,18 +17,18 @@ logger = logging.getLogger(__name__)
# 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"]
"trade_duration", "open_rate", "close_rate", "open_at_end", "exit_reason"]
# Mid-term format, created 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',
'open_rate', 'close_rate', 'open_at_end', 'exit_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',
'profit_ratio', 'profit_abs', 'exit_reason',
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'enter_tag',
'is_short'

View File

@ -914,7 +914,7 @@ class FreqtradeBot(LoggingMixin):
trade.stoploss_order_id = None
logger.error(f'Unable to place a stoploss order on exchange. {e}')
logger.warning('Exiting the trade forcefully')
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
self.execute_trade_exit(trade, trade.stop_loss, exit_reason=SellCheckTuple(
sell_type=SellType.EMERGENCY_SELL))
except ExchangeError:
@ -947,7 +947,7 @@ class FreqtradeBot(LoggingMixin):
# We check if stoploss order is fulfilled
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
# TODO-lev: Update to exit reason
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
trade.exit_reason = SellType.STOPLOSS_ON_EXCHANGE.value
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
stoploss_order=True)
# Lock pair for one candle to prevent immediate rebuys
@ -1102,7 +1102,7 @@ class FreqtradeBot(LoggingMixin):
try:
self.execute_trade_exit(
trade, order.get('price'),
sell_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
exit_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
except DependencyException as exception:
logger.warning(
f'Unable to emergency sell trade {trade.pair}: {exception}')
@ -1266,7 +1266,7 @@ class FreqtradeBot(LoggingMixin):
self,
trade: Trade,
limit: float,
sell_reason: SellCheckTuple,
exit_reason: SellCheckTuple,
*,
exit_tag: Optional[str] = None,
ordertype: Optional[str] = None,
@ -1275,7 +1275,7 @@ class FreqtradeBot(LoggingMixin):
Executes a trade exit for the given trade and limit
:param trade: Trade instance
:param limit: limit rate for the sell order
:param sell_reason: Reason the sell was triggered
:param exit_reason: Reason the sell was triggered
:return: True if it succeeds (supported) False (not supported)
"""
trade.funding_fees = self.exchange.get_funding_fees(
@ -1284,7 +1284,7 @@ class FreqtradeBot(LoggingMixin):
trade.open_date
)
exit_type = 'sell' # TODO-lev: Update to exit
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
if exit_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
exit_type = 'stoploss'
# if stoploss is on exchange and we are on dry_run mode,
@ -1314,7 +1314,7 @@ class FreqtradeBot(LoggingMixin):
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
order_type = ordertype or self.strategy.order_types[exit_type]
if sell_reason.sell_type == SellType.EMERGENCY_SELL:
if exit_reason.sell_type == SellType.EMERGENCY_SELL:
# Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market")
@ -1323,7 +1323,7 @@ class FreqtradeBot(LoggingMixin):
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
time_in_force=time_in_force, sell_reason=sell_reason.sell_reason,
time_in_force=time_in_force, exit_reason=exit_reason.exit_reason,
current_time=datetime.now(timezone.utc)): # TODO-lev: Update to exit
logger.info(f"User requested abortion of exiting {trade.pair}")
return False
@ -1350,7 +1350,7 @@ class FreqtradeBot(LoggingMixin):
trade.open_order_id = order['id']
trade.exit_order_status = ''
trade.close_rate_requested = limit
trade.sell_reason = exit_tag or sell_reason.sell_reason
trade.exit_reason = exit_tag or exit_reason.exit_reason
# Lock pair for one candle to prevent immediate re-trading
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
@ -1395,7 +1395,7 @@ class FreqtradeBot(LoggingMixin):
'profit_ratio': profit_ratio,
'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'sell_reason': trade.sell_reason,
'exit_reason': trade.exit_reason,
'open_date': trade.open_date,
'close_date': trade.close_date or datetime.utcnow(),
'stake_currency': self.config['stake_currency'],
@ -1442,7 +1442,7 @@ class FreqtradeBot(LoggingMixin):
'profit_ratio': profit_ratio,
'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'sell_reason': trade.sell_reason,
'exit_reason': trade.exit_reason,
'open_date': trade.open_date,
'close_date': trade.close_date or datetime.now(timezone.utc),
'stake_currency': self.config['stake_currency'],

View File

@ -411,11 +411,11 @@ class Backtesting:
pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount,
rate=closerate,
time_in_force=time_in_force,
sell_reason=sell.sell_reason,
exit_reason=sell.exit_reason,
current_time=sell_candle_time):
return None
trade.sell_reason = sell.sell_reason
trade.exit_reason = sell.exit_reason
# Checks and adds an exit tag, after checking that the length of the
# sell_row has the length for an exit tag column
@ -424,7 +424,7 @@ class Backtesting:
and sell_row[EXIT_TAG_IDX] is not None
and len(sell_row[EXIT_TAG_IDX]) > 0
):
trade.sell_reason = sell_row[EXIT_TAG_IDX]
trade.exit_reason = sell_row[EXIT_TAG_IDX]
trade.close(closerate, show_msg=False)
return trade
@ -542,7 +542,7 @@ class Backtesting:
sell_row = data[pair][-1]
trade.close_date = sell_row[DATE_IDX].to_pydatetime()
trade.sell_reason = SellType.FORCE_SELL.value
trade.exit_reason = SellType.FORCE_SELL.value
trade.close(sell_row[OPEN_IDX], show_msg=False)
LocalTrade.close_bt_trade(trade)
# Deepcopy object to have wallets update correctly

View File

@ -159,7 +159,7 @@ def generate_tag_metrics(tag_type: str,
return []
def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]:
def generate_exit_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]:
"""
Generate small table outlining Backtest results
:param max_open_trades: Max_open_trades parameter
@ -168,8 +168,8 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
"""
tabular_data = []
for reason, count in results['sell_reason'].value_counts().iteritems():
result = results.loc[results['sell_reason'] == reason]
for reason, count in results['exit_reason'].value_counts().iteritems():
result = results.loc[results['exit_reason'] == reason]
profit_mean = result['profit_ratio'].mean()
profit_sum = result['profit_ratio'].sum()
@ -177,7 +177,7 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
tabular_data.append(
{
'sell_reason': reason,
'exit_reason': reason,
'trades': count,
'wins': len(result[result['profit_abs'] > 0]),
'draws': len(result[result['profit_abs'] == 0]),
@ -383,7 +383,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance,
results=results, skip_nan=False)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
exit_reason_stats = generate_exit_reason_stats(max_open_trades=max_open_trades,
results=results)
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
starting_balance=start_balance,
@ -407,7 +407,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'worst_pair': worst_pair,
'results_per_pair': pair_results,
'results_per_enter_tag': enter_tag_results,
'sell_reason_summary': sell_reason_stats,
'exit_reason_summary': exit_reason_stats,
'left_open_trades': left_open_results,
# 'days_breakdown_stats': days_breakdown_stats,
@ -558,10 +558,10 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str:
def text_table_exit_reason(exit_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str:
"""
Generate small table outlining Backtest results
:param sell_reason_stats: Sell reason metrics
:param exit_reason_stats: Sell reason metrics
:param stake_currency: Stakecurrency used
:return: pretty printed table with tabulate as string
"""
@ -576,12 +576,12 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
]
output = [[
t['sell_reason'], t['trades'],
t['exit_reason'], t['trades'],
_generate_wins_draws_losses(t['wins'], t['draws'], t['losses']),
t['profit_mean_pct'], t['profit_sum_pct'],
round_coin_value(t['profit_total_abs'], stake_currency, False),
t['profit_total_pct'],
] for t in sell_reason_stats]
] for t in exit_reason_stats]
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
@ -788,7 +788,7 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '='))
print(table)
table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'],
table = text_table_exit_reason(exit_reason_stats=results['exit_reason_summary'],
stake_currency=stake_currency)
if isinstance(table, str) and len(table) > 0:
print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))

View File

@ -45,7 +45,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
max_rate = get_column_def(cols, 'max_rate', '0.0')
min_rate = get_column_def(cols, 'min_rate', 'null')
sell_reason = get_column_def(cols, 'sell_reason', 'null')
exit_reason = get_column_def(cols, 'exit_reason', 'null')
strategy = get_column_def(cols, 'strategy', 'null')
enter_tag = get_column_def(cols, 'buy_tag', get_column_def(cols, 'enter_tag', 'null'))
@ -98,7 +98,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
stake_amount, amount, amount_requested, open_date, close_date, open_order_id,
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, exit_order_status, strategy, enter_tag,
max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag,
timeframe, open_trade_value, close_profit_abs,
trading_mode, leverage, isolated_liq, is_short,
interest_rate, funding_fees
@ -114,7 +114,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
{initial_stop_loss} initial_stop_loss,
{initial_stop_loss_pct} initial_stop_loss_pct,
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
{max_rate} max_rate, {min_rate} min_rate, {exit_reason} exit_reason,
{exit_order_status} exit_order_status,
{strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,

View File

@ -261,7 +261,7 @@ class LocalTrade():
max_rate: float = 0.0
# Lowest price reached
min_rate: float = 0.0
sell_reason: str = ''
exit_reason: str = ''
exit_order_status: str = ''
strategy: str = ''
enter_tag: Optional[str] = None
@ -435,7 +435,7 @@ class LocalTrade():
'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
'profit_abs': self.close_profit_abs,
'sell_reason': self.sell_reason,
'exit_reason': self.exit_reason,
'exit_order_status': self.exit_order_status,
'stop_loss_abs': self.stop_loss,
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
@ -575,7 +575,7 @@ class LocalTrade():
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
self.stoploss_order_id = None
self.close_rate_requested = self.stop_loss
self.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
self.exit_reason = SellType.STOPLOSS_ON_EXCHANGE.value
if self.is_open:
logger.info(f'{order_type.upper()} is hit for {self}.')
self.close(safe_value_fallback(order, 'average', 'price'))
@ -936,7 +936,7 @@ class Trade(_DECL_BASE, LocalTrade):
max_rate = Column(Float, nullable=True, default=0.0)
# Lowest price reached
min_rate = Column(Float, nullable=True)
sell_reason = Column(String(100), nullable=True)
exit_reason = Column(String(100), nullable=True)
exit_order_status = Column(String(100), nullable=True)
strategy = Column(String(100), nullable=True)
enter_tag = Column(String(100), nullable=True)
@ -1143,7 +1143,7 @@ class Trade(_DECL_BASE, LocalTrade):
]
@staticmethod
def get_sell_reason_performance(pair: Optional[str]) -> List[Dict[str, Any]]:
def get_exit_reason_performance(pair: Optional[str]) -> List[Dict[str, Any]]:
"""
Returns List of dicts containing all Trades, based on sell reason performance
Can either be average for all pairs or a specific pair provided
@ -1155,30 +1155,30 @@ class Trade(_DECL_BASE, LocalTrade):
filters.append(Trade.pair == pair)
sell_tag_perf = Trade.query.with_entities(
Trade.sell_reason,
Trade.exit_reason,
func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count')
).filter(*filters)\
.group_by(Trade.sell_reason) \
.group_by(Trade.exit_reason) \
.order_by(desc('profit_sum_abs')) \
.all()
return [
{
'sell_reason': sell_reason if sell_reason is not None else "Other",
'exit_reason': exit_reason if exit_reason is not None else "Other",
'profit_ratio': profit,
'profit_pct': round(profit * 100, 2),
'profit_abs': profit_abs,
'count': count
}
for sell_reason, profit, profit_abs, count in sell_tag_perf
for exit_reason, profit, profit_abs, count in sell_tag_perf
]
@staticmethod
def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]:
"""
Returns List of dicts containing all Trades, based on buy_tag + sell_reason performance
Returns List of dicts containing all Trades, based on buy_tag + exit_reason performance
Can either be average for all pairs or a specific pair provided
NOTE: Not supported in Backtesting.
"""
@ -1190,7 +1190,7 @@ class Trade(_DECL_BASE, LocalTrade):
mix_tag_perf = Trade.query.with_entities(
Trade.id,
Trade.enter_tag,
Trade.sell_reason,
Trade.exit_reason,
func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count')
@ -1200,12 +1200,12 @@ class Trade(_DECL_BASE, LocalTrade):
.all()
return_list: List[Dict] = []
for id, enter_tag, sell_reason, profit, profit_abs, count in mix_tag_perf:
for id, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf:
enter_tag = enter_tag if enter_tag is not None else "Other"
sell_reason = sell_reason if sell_reason is not None else "Other"
exit_reason = exit_reason if exit_reason is not None else "Other"
if(sell_reason is not None and enter_tag is not None):
mix_tag = enter_tag + " " + sell_reason
if(exit_reason is not None and enter_tag is not None):
mix_tag = enter_tag + " " + exit_reason
i = 0
if not any(item["mix_tag"] == mix_tag for item in return_list):
return_list.append({'mix_tag': mix_tag,

View File

@ -236,7 +236,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
if trades is not None and len(trades) > 0:
# Create description for sell summarizing the trade
trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, "
f"{row['sell_reason']}, "
f"{row['exit_reason']}, "
f"{row['trade_duration']} min",
axis=1)
trade_buys = go.Scatter(

View File

@ -44,8 +44,8 @@ class StoplossGuard(IProtection):
# filters = [
# Trade.is_open.is_(False),
# Trade.close_date > look_back_until,
# or_(Trade.sell_reason == SellType.STOP_LOSS.value,
# and_(Trade.sell_reason == SellType.TRAILING_STOP_LOSS.value,
# or_(Trade.exit_reason == SellType.STOP_LOSS.value,
# and_(Trade.exit_reason == SellType.TRAILING_STOP_LOSS.value,
# Trade.close_profit < 0))
# ]
# if pair:
@ -54,7 +54,7 @@ class StoplossGuard(IProtection):
# TODO-lev: Liquidation price?
trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until)
trades = [trade for trade in trades1 if (str(trade.sell_reason) in (
trades = [trade for trade in trades1 if (str(trade.exit_reason) in (
SellType.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value,
SellType.STOPLOSS_ON_EXCHANGE.value)
and trade.close_profit and trade.close_profit < 0)]

View File

@ -108,7 +108,7 @@ class SellReason(BaseModel):
class Stats(BaseModel):
sell_reasons: Dict[str, SellReason]
exit_reasons: Dict[str, SellReason]
durations: Dict[str, Union[str, float]]
@ -212,7 +212,7 @@ class TradeSchema(BaseModel):
profit_pct: Optional[float]
profit_abs: Optional[float]
profit_fiat: Optional[float]
sell_reason: Optional[str]
exit_reason: Optional[str]
exit_order_status: Optional[str]
stop_loss_abs: Optional[float]
stop_loss_ratio: Optional[float]

View File

@ -416,11 +416,11 @@ class RPC:
return 'draws'
trades = trades = Trade.get_trades([Trade.is_open.is_(False)])
# Sell reason
sell_reasons = {}
exit_reasons = {}
for trade in trades:
if trade.sell_reason not in sell_reasons:
sell_reasons[trade.sell_reason] = {'wins': 0, 'losses': 0, 'draws': 0}
sell_reasons[trade.sell_reason][trade_win_loss(trade)] += 1
if trade.exit_reason not in exit_reasons:
exit_reasons[trade.exit_reason] = {'wins': 0, 'losses': 0, 'draws': 0}
exit_reasons[trade.exit_reason][trade_win_loss(trade)] += 1
# Duration
dur: Dict[str, List[int]] = {'wins': [], 'draws': [], 'losses': []}
@ -434,7 +434,7 @@ class RPC:
losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else 'N/A'
durations = {'wins': wins_dur, 'draws': draws_dur, 'losses': losses_dur}
return {'sell_reasons': sell_reasons, 'durations': durations}
return {'exit_reasons': exit_reasons, 'durations': durations}
def _rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str,
@ -672,12 +672,12 @@ class RPC:
closing_side = "buy" if trade.is_short else "sell"
current_rate = self._freqtrade.exchange.get_rate(
trade.pair, refresh=False, side=closing_side)
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
exit_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
order_type = ordertype or self._freqtrade.strategy.order_types.get(
"forcesell", self._freqtrade.strategy.order_types["sell"])
self._freqtrade.execute_trade_exit(
trade, current_rate, sell_reason, ordertype=order_type)
trade, current_rate, exit_reason, ordertype=order_type)
# ---- EOF def _exec_forcesell ----
if self._freqtrade.state != State.RUNNING:
@ -799,16 +799,16 @@ class RPC:
"""
return Trade.get_enter_tag_performance(pair)
def _rpc_sell_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]:
def _rpc_exit_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]:
"""
Handler for sell reason performance.
Shows a performance statistic from finished trades
"""
return Trade.get_sell_reason_performance(pair)
return Trade.get_exit_reason_performance(pair)
def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]:
"""
Handler for mix tag (enter_tag + sell_reason) performance.
Handler for mix tag (enter_tag + exit_reason) performance.
Shows a performance statistic from finished trades
"""
mix_tags = Trade.get_mix_tag_performance(pair)

View File

@ -156,7 +156,7 @@ class Telegram(RPCHandler):
CommandHandler('delete', self._delete_trade),
CommandHandler('performance', self._performance),
CommandHandler(['buys', 'entries'], self._enter_tag_performance),
CommandHandler('sells', self._sell_reason_performance),
CommandHandler('sells', self._exit_reason_performance),
CommandHandler('mix_tags', self._mix_tag_performance),
CommandHandler('stats', self._stats),
CommandHandler('daily', self._daily),
@ -186,8 +186,8 @@ class Telegram(RPCHandler):
CallbackQueryHandler(self._performance, pattern='update_performance'),
CallbackQueryHandler(self._enter_tag_performance,
pattern='update_enter_tag_performance'),
CallbackQueryHandler(self._sell_reason_performance,
pattern='update_sell_reason_performance'),
CallbackQueryHandler(self._exit_reason_performance,
pattern='update_exit_reason_performance'),
CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'),
CallbackQueryHandler(self._count, pattern='update_count'),
CallbackQueryHandler(self._forcebuy_inline),
@ -284,7 +284,7 @@ class Telegram(RPCHandler):
f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
f"*Enter Tag:* `{msg['enter_tag']}`\n"
f"*Exit Reason:* `{msg['sell_reason']}`\n"
f"*Exit Reason:* `{msg['exit_reason']}`\n"
f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n"
f"*Direction:* `{msg['direction']}`\n"
f"{msg['leverage_text']}"
@ -355,7 +355,7 @@ class Telegram(RPCHandler):
if isinstance(sell_noti, str):
noti = sell_noti
else:
noti = sell_noti.get(str(msg['sell_reason']), default_noti)
noti = sell_noti.get(str(msg['exit_reason']), default_noti)
else:
noti = self._config['telegram'] \
.get('notification_settings', {}).get(str(msg_type), default_noti)
@ -378,7 +378,7 @@ class Telegram(RPCHandler):
return "\N{ROCKET}"
elif float(msg['profit_percent']) >= 0.0:
return "\N{EIGHT SPOKED ASTERISK}"
elif msg['sell_reason'] == "stop_loss":
elif msg['exit_reason'] == "stop_loss":
return "\N{WARNING SIGN}"
else:
return "\N{CROSS MARK}"
@ -697,23 +697,23 @@ class Telegram(RPCHandler):
'force_sell': 'Forcesell',
'emergency_sell': 'Emergency Sell',
}
sell_reasons_tabulate = [
exit_reasons_tabulate = [
[
reason_map.get(reason, reason),
sum(count.values()),
count['wins'],
count['losses']
] for reason, count in stats['sell_reasons'].items()
] for reason, count in stats['exit_reasons'].items()
]
sell_reasons_msg = 'No trades yet.'
for reason in chunks(sell_reasons_tabulate, 25):
sell_reasons_msg = tabulate(
exit_reasons_msg = 'No trades yet.'
for reason in chunks(exit_reasons_tabulate, 25):
exit_reasons_msg = tabulate(
reason,
headers=['Sell Reason', 'Sells', 'Wins', 'Losses']
)
if len(sell_reasons_tabulate) > 25:
self._send_msg(sell_reasons_msg, ParseMode.MARKDOWN)
sell_reasons_msg = ''
if len(exit_reasons_tabulate) > 25:
self._send_msg(exit_reasons_msg, ParseMode.MARKDOWN)
exit_reasons_msg = ''
durations = stats['durations']
duration_msg = tabulate(
@ -725,7 +725,7 @@ class Telegram(RPCHandler):
],
headers=['', 'Avg. Duration']
)
msg = (f"""```\n{sell_reasons_msg}```\n```\n{duration_msg}```""")
msg = (f"""```\n{exit_reasons_msg}```\n```\n{duration_msg}```""")
self._send_msg(msg, ParseMode.MARKDOWN)
@ -1026,7 +1026,7 @@ class Telegram(RPCHandler):
self._send_msg(str(e))
@authorized_only
def _sell_reason_performance(self, update: Update, context: CallbackContext) -> None:
def _exit_reason_performance(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /sells.
Shows a performance statistic from finished trades
@ -1039,11 +1039,11 @@ class Telegram(RPCHandler):
if context.args and isinstance(context.args[0], str):
pair = context.args[0]
trades = self._rpc._rpc_sell_reason_performance(pair)
trades = self._rpc._rpc_exit_reason_performance(pair)
output = "<b>Sell Reason Performance:</b>\n"
for i, trade in enumerate(trades):
stat_line = (
f"{i+1}.\t <code>{trade['sell_reason']}\t"
f"{i+1}.\t <code>{trade['exit_reason']}\t"
f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "
f"({trade['profit_ratio']:.2%}) "
f"({trade['count']})</code>\n")
@ -1055,7 +1055,7 @@ class Telegram(RPCHandler):
output += stat_line
self._send_msg(output, parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_sell_reason_performance",
reload_able=True, callback_path="update_exit_reason_performance",
query=update.callback_query)
except RPCException as e:
self._send_msg(str(e))

View File

@ -35,11 +35,11 @@ class SellCheckTuple:
NamedTuple for Sell type + reason
"""
sell_type: SellType
sell_reason: str = ''
exit_reason: str = ''
def __init__(self, sell_type: SellType, sell_reason: str = ''):
def __init__(self, sell_type: SellType, exit_reason: str = ''):
self.sell_type = sell_type
self.sell_reason = sell_reason or sell_type.value
self.exit_reason = exit_reason or sell_type.value
@property
def sell_flag(self):
@ -256,7 +256,7 @@ class IStrategy(ABC, HyperStrategyMixin):
return True
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str,
rate: float, time_in_force: str, exit_reason: str,
current_time: datetime, **kwargs) -> bool:
"""
Called right before placing a regular exit order.
@ -273,7 +273,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param amount: Amount in quote currency.
:param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param sell_reason: Exit reason.
:param exit_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell']
:param current_time: datetime object, containing the current datetime
@ -815,7 +815,7 @@ class IStrategy(ABC, HyperStrategyMixin):
logger.debug(f"{trade.pair} - Sell signal received. "
f"sell_type=SellType.{sell_signal.name}" +
(f", custom_reason={custom_reason}" if custom_reason else ""))
return SellCheckTuple(sell_type=sell_signal, sell_reason=custom_reason)
return SellCheckTuple(sell_type=sell_signal, exit_reason=custom_reason)
# Sequence:
# Exit-signal

View File

@ -187,7 +187,7 @@
"trades = load_backtest_data(backtest_dir)\n",
"\n",
"# Show value-counts per pair\n",
"trades.groupby(\"pair\")[\"sell_reason\"].value_counts()"
"trades.groupby(\"pair\")[\"exit_reason\"].value_counts()"
]
},
{
@ -257,7 +257,7 @@
"trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n",
"\n",
"# Display results\n",
"trades.groupby(\"pair\")[\"sell_reason\"].value_counts()"
"trades.groupby(\"pair\")[\"exit_reason\"].value_counts()"
]
},
{

View File

@ -103,7 +103,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
return True
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str,
rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -> bool:
"""
Called right before placing a regular sell order.
@ -120,7 +120,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
:param amount: Amount in quote currency.
:param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param sell_reason: Sell reason.
:param exit_reason: Sell reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell']
:param current_time: datetime object, containing the current datetime

View File

@ -103,7 +103,7 @@ def mock_trade_2(fee, is_short: bool):
strategy='StrategyTestV3',
timeframe=5,
enter_tag='TEST1',
sell_reason='sell_signal',
exit_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
is_short=is_short
@ -163,7 +163,7 @@ def mock_trade_3(fee, is_short: bool):
is_open=False,
strategy='StrategyTestV3',
timeframe=5,
sell_reason='roi',
exit_reason='roi',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc),
is_short=is_short
@ -400,7 +400,7 @@ def short_trade(fee):
open_order_id='dry_run_exit_short_12345',
strategy='DefaultStrategy',
timeframe=5,
sell_reason='sell_signal', # TODO-lev: Update to exit/close reason
exit_reason='sell_signal', # TODO-lev: Update to exit/close reason
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
# close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
is_short=True
@ -489,7 +489,7 @@ def leverage_trade(fee):
open_order_id='dry_run_leverage_buy_12368',
strategy='DefaultStrategy',
timeframe=5,
sell_reason='sell_signal',
exit_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300),
close_date=datetime.now(tz=timezone.utc),
interest_rate=0.0005

View File

@ -89,7 +89,7 @@ def mock_trade_usdt_2(fee):
open_order_id='dry_run_sell_12345',
strategy='StrategyTestV2',
timeframe=5,
sell_reason='sell_signal',
exit_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
)
@ -148,7 +148,7 @@ def mock_trade_usdt_3(fee):
is_open=False,
strategy='StrategyTestV2',
timeframe=5,
sell_reason='roi',
exit_reason='roi',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc),
)

View File

@ -95,8 +95,8 @@ tc1 = BTContainer(data=[
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
],
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
)
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
@ -107,7 +107,7 @@ tc2 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
)
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
@ -118,7 +118,7 @@ tc3 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
)
# 5) Stoploss and sell are hit. should sell on stoploss
@ -129,7 +129,7 @@ tc4 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
)
TESTS = [
@ -162,7 +162,7 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.exit_type == trade.sell_reason
assert res.exit_type == trade.exit_reason
assert res.open_date == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None)

View File

@ -15,7 +15,7 @@ class BTrade(NamedTuple):
"""
Minimalistic Trade result used for functional backtesting
"""
sell_reason: SellType
exit_reason: SellType
open_tick: int
close_tick: int
enter_tag: Optional[str] = None

View File

@ -44,7 +44,7 @@ def hyperopt_results():
'profit_abs': [-0.2, 0.4, -0.2, 0.6],
'trade_duration': [10, 30, 10, 10],
'amount': [0.1, 0.1, 0.1, 0.1],
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.STOP_LOSS, SellType.ROI],
'exit_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.STOP_LOSS, SellType.ROI],
'open_date':
[
datetime(2019, 1, 1, 9, 15, 0),

View File

@ -22,7 +22,7 @@ tc0 = BTContainer(data=[
[4, 5010, 5011, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 1: Stop-Loss Triggered 1% loss
@ -36,7 +36,7 @@ tc1 = BTContainer(data=[
[4, 4977, 4995, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
)
@ -51,7 +51,7 @@ tc2 = BTContainer(data=[
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
)
@ -71,8 +71,8 @@ tc3 = BTContainer(data=[
[5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit
[6, 4950, 4975, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(exit_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
)
# Test 4: Minus 3% / recovery +15%
@ -88,7 +88,7 @@ tc4 = BTContainer(data=[
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 5: Drops 0.5% Closes +20%, ROI triggers 3% Gain
@ -102,7 +102,7 @@ tc5 = BTContainer(data=[
[4, 4962, 4987, 4962, 4972, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
# Test 6: Drops 3% / Recovers 6% Positive / Closes 1% positve, Stop-Loss triggers 2% Loss
@ -116,7 +116,7 @@ tc6 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 7: 6% Positive / 1% Negative / Close 1% Positve, ROI Triggers 3% Gain
@ -130,7 +130,7 @@ tc7 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=2)]
)
@ -144,7 +144,7 @@ tc8 = BTContainer(data=[
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
@ -158,7 +158,7 @@ tc9 = BTContainer(data=[
[3, 5000, 5200, 4550, 4850, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 10: trailing_stop should raise so candle 3 causes a stoploss
@ -174,7 +174,7 @@ tc10 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=4)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=4)]
)
# Test 11: trailing_stop should raise so candle 3 causes a stoploss
@ -190,7 +190,7 @@ tc11 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 12: trailing_stop should raise in candle 2 and cause a stoploss in the same candle
@ -206,7 +206,7 @@ tc12 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 13: Buy and sell ROI on same candle
@ -219,7 +219,7 @@ tc13 = BTContainer(data=[
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4750, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=1)]
)
# Test 14 - Buy and Stoploss on same candle
@ -232,7 +232,7 @@ tc14 = BTContainer(data=[
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
)
@ -246,8 +246,8 @@ tc15 = BTContainer(data=[
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=1),
BTrade(exit_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)]
)
# Test 16: Buy, hold for 65 min, then forcesell using roi=-1
@ -262,7 +262,7 @@ tc16 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
# Test 17: Buy, hold for 120 mins, then forcesell using roi=-1
@ -278,7 +278,7 @@ tc17 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
@ -294,7 +294,7 @@ tc18 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
# Test 19: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3.
@ -309,7 +309,7 @@ tc19 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4550, 4975, 4550, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
# Test 20: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3.
@ -324,7 +324,7 @@ tc20 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4925, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
# Test 21: trailing_stop ROI collision.
@ -341,7 +341,7 @@ tc21 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=2)]
)
# Test 22: trailing_stop Raises in candle 2 - but ROI applies at the same time.
@ -357,7 +357,7 @@ tc22 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=2)]
)
# Test 23: trailing_stop Raises in candle 2 (does not trigger)
@ -376,7 +376,7 @@ tc23 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
# Test 24: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
@ -391,7 +391,7 @@ tc24 = BTContainer(data=[
[4, 5010, 5010, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
@ -406,7 +406,7 @@ tc25 = BTContainer(data=[
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 26: Sell with signal sell in candle 3 (ROI at signal candle)
@ -421,7 +421,7 @@ tc26 = BTContainer(data=[
[4, 5010, 5010, 4855, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
# Test 27: Sell with signal sell in candle 3 (ROI at signal candle)
@ -435,7 +435,7 @@ tc27 = BTContainer(data=[
[4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 28: trailing_stop should raise so candle 3 causes a stoploss
@ -452,7 +452,7 @@ tc28 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 29: trailing_stop should be triggered by low of next candle, without adjusting stoploss using
@ -467,7 +467,7 @@ tc29 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 30: trailing_stop should be triggered immediately on trade open candle.
@ -481,7 +481,7 @@ tc30 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_stop_positive=0.01,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 31: trailing_stop should be triggered immediately on trade open candle.
@ -496,7 +496,7 @@ tc31 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 32: trailing_stop should be triggered immediately on trade open candle.
@ -511,7 +511,7 @@ tc32 = BTContainer(data=[
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 33: trailing_stop should be triggered immediately on trade open candle.
@ -527,7 +527,7 @@ tc33 = BTContainer(data=[
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(
sell_reason=SellType.TRAILING_STOP_LOSS,
exit_reason=SellType.TRAILING_STOP_LOSS,
open_tick=1,
close_tick=1,
enter_tag='buy_signal_01'
@ -548,7 +548,7 @@ tc34 = BTContainer(data=[
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
leverage=5.0,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
TESTS = [
@ -641,7 +641,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.sell_reason == trade.sell_reason.value
assert res.exit_reason == trade.exit_reason.value
assert res.enter_tag == trade.enter_tag
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)

View File

@ -628,7 +628,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
# No data available.
res = backtesting._get_sell_trade_entry(trade, row_sell)
assert res is not None
assert res.sell_reason == SellType.ROI.value
assert res.exit_reason == SellType.ROI.value
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
# Enter new trade
@ -647,7 +647,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
res = backtesting._get_sell_trade_entry(trade, row_sell)
assert res is not None
assert res.sell_reason == SellType.ROI.value
assert res.exit_reason == SellType.ROI.value
# Sell at minute 3 (not available above!)
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
assert round(res.close_rate, 3) == round(209.0225, 3)
@ -693,7 +693,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'trade_duration': [235, 40],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'sell_reason': [SellType.ROI.value, SellType.ROI.value],
'exit_reason': [SellType.ROI.value, SellType.ROI.value],
'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236],
@ -1002,7 +1002,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
text_table_mock = MagicMock()
sell_reason_mock = MagicMock()
exit_reason_mock = MagicMock()
strattable_mock = MagicMock()
strat_summary = MagicMock()
@ -1010,7 +1010,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
text_table_bt_results=text_table_mock,
text_table_strategy=strattable_mock,
generate_pair_metrics=MagicMock(),
generate_sell_reason_stats=sell_reason_mock,
generate_exit_reason_stats=exit_reason_mock,
generate_strategy_comparison=strat_summary,
generate_daily_stats=MagicMock(),
)
@ -1035,7 +1035,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
assert backtestmock.call_count == 2
assert text_table_mock.call_count == 4
assert strattable_mock.call_count == 1
assert sell_reason_mock.call_count == 2
assert exit_reason_mock.call_count == 2
assert strat_summary.call_count == 1
# check the logs, that will contain the backtest result
@ -1081,7 +1081,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'close_rate': [0.104969, 0.103541],
"is_short": [False, False],
'sell_reason': [SellType.ROI, SellType.ROI]
'exit_reason': [SellType.ROI, SellType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1],
@ -1099,7 +1099,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
"is_short": [False, False, False],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{
@ -1192,7 +1192,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI]
'exit_reason': [SellType.ROI, SellType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1],
@ -1210,7 +1210,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{

View File

@ -359,7 +359,7 @@ def test_hyperopt_format_results(hyperopt):
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
"exit_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
}),
'config': hyperopt.config,
@ -428,7 +428,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
"exit_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
}),
'config': hyperopt_conf,

View File

@ -18,11 +18,11 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene
generate_daily_stats, generate_edge_table,
generate_pair_metrics,
generate_periodic_breakdown_stats,
generate_sell_reason_stats,
generate_exit_reason_stats,
generate_strategy_comparison,
generate_trading_stats, show_sorted_pairlist,
store_backtest_stats, text_table_bt_results,
text_table_sell_reason, text_table_strategy)
text_table_exit_reason, text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
from tests.conftest import CURRENT_TEST_STRATEGY
from tests.data.test_history import _backup_file, _clean_test_file
@ -78,7 +78,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
"exit_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
}),
'config': default_conf,
@ -127,7 +127,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.ROI,
"exit_reason": [SellType.ROI, SellType.ROI,
SellType.STOP_LOSS, SellType.FORCE_SELL]
}),
'config': default_conf,
@ -260,7 +260,7 @@ def test_generate_trading_stats(testdatadir):
assert res['losses'] == 0
def test_text_table_sell_reason():
def test_text_table_exit_reason():
results = pd.DataFrame(
{
@ -271,7 +271,7 @@ def test_text_table_sell_reason():
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
@ -286,13 +286,13 @@ def test_text_table_sell_reason():
' -0.2 | -5 |'
)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
results=results)
assert text_table_sell_reason(sell_reason_stats=sell_reason_stats,
assert text_table_exit_reason(exit_reason_stats=exit_reason_stats,
stake_currency='BTC') == result_str
def test_generate_sell_reason_stats():
def test_generate_exit_reason_stats():
results = pd.DataFrame(
{
@ -303,23 +303,23 @@ def test_generate_sell_reason_stats():
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI.value, SellType.ROI.value, SellType.STOP_LOSS.value]
'exit_reason': [SellType.ROI.value, SellType.ROI.value, SellType.STOP_LOSS.value]
}
)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
results=results)
roi_result = sell_reason_stats[0]
assert roi_result['sell_reason'] == 'roi'
roi_result = exit_reason_stats[0]
assert roi_result['exit_reason'] == 'roi'
assert roi_result['trades'] == 2
assert pytest.approx(roi_result['profit_mean']) == 0.15
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
assert pytest.approx(roi_result['profit_mean']) == 0.15
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
stop_result = sell_reason_stats[1]
stop_result = exit_reason_stats[1]
assert stop_result['sell_reason'] == 'stop_loss'
assert stop_result['exit_reason'] == 'stop_loss'
assert stop_result['trades'] == 1
assert pytest.approx(stop_result['profit_mean']) == -0.1
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
@ -343,7 +343,7 @@ def test_text_table_strategy(default_conf):
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
), 'config': default_conf}
results['TestStrategy2'] = {'results': pd.DataFrame(
@ -356,7 +356,7 @@ def test_text_table_strategy(default_conf):
'wins': [4, 1, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
), 'config': default_conf}

View File

@ -11,7 +11,7 @@ from tests.conftest import get_patched_freqtradebot, log_has_re
def generate_mock_trade(pair: str, fee: float, is_open: bool,
sell_reason: str = SellType.SELL_SIGNAL,
exit_reason: str = SellType.SELL_SIGNAL,
min_ago_open: int = None, min_ago_close: int = None,
profit_rate: float = 0.9
):
@ -32,7 +32,7 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
trade.recalc_open_trade_value()
if not is_open:
trade.close(open_rate * profit_rate)
trade.sell_reason = sell_reason
trade.exit_reason = exit_reason
return trade
@ -91,7 +91,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30,
))
@ -100,12 +100,12 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear()
# This trade does not count, as it's closed too long ago
Trade.query.session.add(generate_mock_trade(
'BCH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'BCH/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100,
))
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'ETH/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30,
))
# 3 Trades closed - but the 2nd has been closed too long ago.
@ -114,7 +114,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'LTC/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'LTC/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30,
))
@ -148,7 +148,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
caplog.clear()
Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
pair, fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, profit_rate=0.9,
))
@ -158,12 +158,12 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
caplog.clear()
# This trade does not count, as it's closed too long ago
Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
pair, fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100, profit_rate=0.9,
))
# Trade does not count for per pair stop as it's the wrong pair.
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'ETH/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30, profit_rate=0.9,
))
# 3 Trades closed - but the 2nd has been closed too long ago.
@ -178,7 +178,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
# 2nd Trade that counts with correct pair
Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
pair, fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30, profit_rate=0.9,
))
@ -203,7 +203,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30,
))
@ -213,7 +213,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
'ETH/BTC', fee.return_value, False, exit_reason=SellType.ROI.value,
min_ago_open=205, min_ago_close=35,
))
@ -242,7 +242,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=800, min_ago_close=450, profit_rate=0.9,
))
@ -253,7 +253,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=120, profit_rate=0.9,
))
@ -265,14 +265,14 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
# Add positive trade
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.ROI.value,
min_ago_open=20, min_ago_close=10, profit_rate=1.15,
))
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
assert not PairLocks.is_pair_locked('XRP/BTC')
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=110, min_ago_close=20, profit_rate=0.8,
))
@ -300,15 +300,15 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
))
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'ETH/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
))
Trade.query.session.add(generate_mock_trade(
'NEO/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'NEO/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
))
# No losing trade yet ... so max_drawdown will raise exception
@ -316,7 +316,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=500, min_ago_close=400, profit_rate=0.9,
))
# Not locked with one trade
@ -326,7 +326,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=1200, min_ago_close=1100, profit_rate=0.5,
))
@ -339,7 +339,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
# Winning trade ... (should not lock, does not change drawdown!)
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.ROI.value,
min_ago_open=320, min_ago_close=410, profit_rate=1.5,
))
assert not freqtrade.protections.global_stop()
@ -349,7 +349,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
# Add additional negative trade, causing a loss of > 15%
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
'XRP/BTC', fee.return_value, False, exit_reason=SellType.ROI.value,
min_ago_open=20, min_ago_close=10, profit_rate=0.8,
))
assert not freqtrade.protections.stop_per_pair('XRP/BTC')

View File

@ -64,7 +64,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_rate_requested': ANY,
'open_trade_value': 0.0010025,
'close_rate_requested': ANY,
'sell_reason': ANY,
'exit_reason': ANY,
'exit_order_status': ANY,
'min_rate': ANY,
'max_rate': ANY,
@ -138,7 +138,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_rate_requested': ANY,
'open_trade_value': ANY,
'close_rate_requested': ANY,
'sell_reason': ANY,
'exit_reason': ANY,
'exit_order_status': ANY,
'min_rate': ANY,
'max_rate': ANY,
@ -916,7 +916,7 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
assert prec_satoshi(res[0]['profit_pct'], 0.5)
def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, fee,
def test_exit_reason_performance_handle(default_conf, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
@ -943,23 +943,23 @@ def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, f
trade.close_date = datetime.utcnow()
trade.is_open = False
res = rpc._rpc_sell_reason_performance(None)
res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 1
assert res[0]['sell_reason'] == 'Other'
assert res[0]['exit_reason'] == 'Other'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.sell_reason = "TEST1"
res = rpc._rpc_sell_reason_performance(None)
trade.exit_reason = "TEST1"
res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 1
assert res[0]['sell_reason'] == 'TEST1'
assert res[0]['exit_reason'] == 'TEST1'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee):
def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@ -970,21 +970,21 @@ def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee):
create_mock_trades(fee)
rpc = RPC(freqtradebot)
res = rpc._rpc_sell_reason_performance(None)
res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 2
assert res[0]['sell_reason'] == 'sell_signal'
assert res[0]['exit_reason'] == 'sell_signal'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 0.5)
assert res[1]['sell_reason'] == 'roi'
assert res[1]['exit_reason'] == 'roi'
assert res[1]['count'] == 1
assert prec_satoshi(res[1]['profit_pct'], 1.0)
# Test for a specific pair
res = rpc._rpc_sell_reason_performance('ETC/BTC')
res = rpc._rpc_exit_reason_performance('ETC/BTC')
assert len(res) == 1
assert res[0]['count'] == 1
assert res[0]['sell_reason'] == 'sell_signal'
assert res[0]['exit_reason'] == 'sell_signal'
assert prec_satoshi(res[0]['profit_pct'], 0.5)
@ -1023,7 +1023,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL"
trade.exit_reason = "TESTSELL"
res = rpc._rpc_mix_tag_performance(None)
assert len(res) == 1

View File

@ -816,14 +816,14 @@ def test_api_stats(botclient, mocker, ticker, fee, markets, is_short):
rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200)
assert 'durations' in rc.json()
assert 'sell_reasons' in rc.json()
assert 'exit_reasons' in rc.json()
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200)
assert 'durations' in rc.json()
assert 'sell_reasons' in rc.json()
assert 'exit_reasons' in rc.json()
assert 'wins' in rc.json()['durations']
assert 'losses' in rc.json()['durations']
@ -955,7 +955,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'open_order_id': open_order_id,
'open_rate_requested': ANY,
'open_trade_value': open_trade_value,
'sell_reason': None,
'exit_reason': None,
'exit_order_status': None,
'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None,
@ -1146,7 +1146,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'open_order_id': '123456',
'open_rate_requested': None,
'open_trade_value': 0.24605460,
'sell_reason': None,
'exit_reason': None,
'exit_order_status': None,
'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None,

View File

@ -962,7 +962,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'fiat_currency': 'USD',
'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'exit_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@ -1030,7 +1030,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'fiat_currency': 'USD',
'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'exit_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@ -1088,7 +1088,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'fiat_currency': 'USD',
'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'exit_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@ -1276,7 +1276,7 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee,
assert "Error" in msg_mock.call_args_list[0][0][0]
def test_telegram_sell_reason_performance_handle(default_conf, update, ticker, fee,
def test_telegram_exit_reason_performance_handle(default_conf, update, ticker, fee,
limit_buy_order, limit_sell_order, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@ -1294,26 +1294,26 @@ def test_telegram_sell_reason_performance_handle(default_conf, update, ticker, f
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
trade.sell_reason = 'TESTSELL'
trade.exit_reason = 'TESTSELL'
# Simulate fulfilled LIMIT_SELL order for trade
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
context = MagicMock()
telegram._sell_reason_performance(update=update, context=context)
telegram._exit_reason_performance(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Sell Reason Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
context.args = [trade.pair]
telegram._sell_reason_performance(update=update, context=context)
telegram._exit_reason_performance(update=update, context=context)
assert msg_mock.call_count == 2
msg_mock.reset_mock()
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_sell_reason_performance',
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_exit_reason_performance',
side_effect=RPCException('Error'))
telegram._sell_reason_performance(update=update, context=MagicMock())
telegram._exit_reason_performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert "Error" in msg_mock.call_args_list[0][0][0]
@ -1338,7 +1338,7 @@ def test_telegram_mix_tag_performance_handle(default_conf, update, ticker, fee,
trade.update(limit_buy_order)
trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL"
trade.exit_reason = "TESTSELL"
# Simulate fulfilled LIMIT_SELL order for trade
trade.update(limit_sell_order)
@ -1826,7 +1826,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'stake_currency': 'ETH',
'fiat_currency': 'USD',
'enter_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'exit_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1),
'close_date': arrow.utcnow(),
})
@ -1860,7 +1860,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'enter_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'exit_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(),
})
@ -1939,7 +1939,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'enter_tag': enter_signal,
'sell_reason': SellType.STOP_LOSS.value,
'exit_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(),
})
@ -2063,7 +2063,7 @@ def test_send_msg_sell_notification_no_fiat(
'stake_currency': 'ETH',
'fiat_currency': 'USD',
'enter_tag': enter_signal,
'sell_reason': SellType.STOP_LOSS.value,
'exit_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
'close_date': arrow.utcnow(),
})
@ -2085,13 +2085,13 @@ def test_send_msg_sell_notification_no_fiat(
@pytest.mark.parametrize('msg,expected', [
({'profit_percent': 20.1, 'sell_reason': 'roi'}, "\N{ROCKET}"),
({'profit_percent': 5.1, 'sell_reason': 'roi'}, "\N{ROCKET}"),
({'profit_percent': 2.56, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': 1.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': 0.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': -5.0, 'sell_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
({'profit_percent': -2.0, 'sell_reason': 'sell_signal'}, "\N{CROSS MARK}"),
({'profit_percent': 20.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
({'profit_percent': 5.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
({'profit_percent': 2.56, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': 1.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': 0.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': -5.0, 'exit_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
({'profit_percent': -2.0, 'exit_reason': 'sell_signal'}, "\N{CROSS MARK}"),
])
def test__sell_emoji(default_conf, mocker, msg, expected):
del default_conf['fiat_display_currency']

View File

@ -244,7 +244,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'exit_reason': SellType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
@ -269,7 +269,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'exit_reason': SellType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
@ -294,7 +294,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'exit_reason': SellType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1

View File

@ -46,7 +46,7 @@ def test_strategy_test_v3(result, fee, is_short, side):
current_time=datetime.utcnow(),
side=side) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', sell_reason='roi',
rate=20000, time_in_force='gtc', exit_reason='roi',
current_time=datetime.utcnow(),
side=side) is True

View File

@ -488,7 +488,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
low=None, high=None)
assert res.sell_flag is True
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_reason == 'custom_sell'
assert res.exit_reason == 'custom_sell'
strategy.custom_sell = MagicMock(return_value='hello world')
@ -497,7 +497,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
low=None, high=None)
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_flag is True
assert res.sell_reason == 'hello world'
assert res.exit_reason == 'hello world'
caplog.clear()
strategy.custom_sell = MagicMock(return_value='h' * 100)
@ -506,7 +506,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
low=None, high=None)
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_flag is True
assert res.sell_reason == 'h' * 64
assert res.exit_reason == 'h' * 64
assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)

View File

@ -234,7 +234,7 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
assert freqtrade.handle_trade(trade) is not ignore_strat_sl
if not ignore_strat_sl:
assert log_has_re('Exit for NEO/BTC detected. Reason: stop_loss.*', caplog)
assert trade.sell_reason == SellType.STOP_LOSS.value
assert trade.exit_reason == SellType.STOP_LOSS.value
def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -> None:
@ -1164,7 +1164,7 @@ def test_create_stoploss_order_invalid_order(
caplog.clear()
freqtrade.create_stoploss_order(trade, 200)
assert trade.stoploss_order_id is None
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
assert trade.exit_reason == SellType.EMERGENCY_SELL.value
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
assert log_has("Exiting the trade forcefully", caplog)
@ -1176,7 +1176,7 @@ def test_create_stoploss_order_invalid_order(
# Rpc is sending first buy, then sell
assert rpc_mock.call_count == 2
assert rpc_mock.call_args_list[1][0][0]['sell_reason'] == SellType.EMERGENCY_SELL.value
assert rpc_mock.call_args_list[1][0][0]['exit_reason'] == SellType.EMERGENCY_SELL.value
assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
@ -1977,7 +1977,7 @@ def test_handle_trade(
assert trade.close_profit == close_profit
assert trade.calc_profit() == 5.685
assert trade.close_date is not None
assert trade.sell_reason == 'sell_signal1'
assert trade.exit_reason == 'sell_signal1'
@pytest.mark.parametrize("is_short", [False, True])
@ -2855,7 +2855,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
freqtrade.execute_trade_exit(
trade=trade,
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
exit_reason=SellCheckTuple(sell_type=SellType.ROI)
)
assert rpc_mock.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
@ -2867,7 +2867,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
freqtrade.execute_trade_exit(
trade=trade,
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
exit_reason=SellCheckTuple(sell_type=SellType.ROI)
)
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
@ -2892,7 +2892,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'profit_ratio': 0.00493809 if is_short else 0.09451372,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value,
'exit_reason': SellType.ROI.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@ -2928,7 +2928,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
)
freqtrade.execute_trade_exit(
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
@ -2951,7 +2951,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'profit_ratio': -0.0945681 if is_short else -1.247e-05,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value,
'exit_reason': SellType.STOP_LOSS.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@ -3003,7 +3003,7 @@ def test_execute_trade_exit_custom_exit_price(
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)
exit_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)
)
# Sell price must be different to default bid price
@ -3031,7 +3031,7 @@ def test_execute_trade_exit_custom_exit_price(
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.SELL_SIGNAL.value,
'exit_reason': SellType.SELL_SIGNAL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@ -3074,7 +3074,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
freqtrade.execute_trade_exit(
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
@ -3098,7 +3098,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'profit_ratio': -0.00501253 if is_short else -0.01493766,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value,
'exit_reason': SellType.STOP_LOSS.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@ -3134,7 +3134,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
trade.stoploss_order_id = "abcd"
freqtrade.execute_trade_exit(trade=trade, limit=1234,
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert create_order_mock.call_count == 2
assert log_has('Could not cancel stoploss order abcd', caplog)
@ -3189,7 +3189,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
)
trade = Trade.query.first()
@ -3265,7 +3265,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt
freqtrade.exit_positions(trades)
assert trade.stoploss_order_id is None
assert trade.is_open is False
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
assert trade.exit_reason == SellType.STOPLOSS_ON_EXCHANGE.value
assert rpc_mock.call_count == 3
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
@ -3328,7 +3328,7 @@ def test_execute_trade_exit_market_order(
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
exit_reason=SellCheckTuple(sell_type=SellType.ROI)
)
assert not trade.is_open
@ -3355,7 +3355,7 @@ def test_execute_trade_exit_market_order(
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value,
'exit_reason': SellType.ROI.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@ -3392,11 +3392,11 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
fetch_ticker=ticker_usdt_sell_up
)
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
exit_reason = SellCheckTuple(sell_type=SellType.ROI)
assert not freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=sell_reason
exit_reason=exit_reason
)
assert mock_insuf.call_count == 1
@ -3561,7 +3561,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
)
trade.close(ticker_usdt_sell_down()['bid'])
assert freqtrade.strategy.is_pair_locked(trade.pair)
@ -3616,7 +3616,7 @@ def test_ignore_roi_if_enter_signal(default_conf_usdt, limit_order, limit_order_
else:
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.ROI.value
assert trade.exit_reason == SellType.ROI.value
@pytest.mark.parametrize("is_short,val1,val2", [
@ -3678,7 +3678,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open,
f"stoploss is {(2.0 * val1 * stop_multi):6f}, "
f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
caplog)
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
assert trade.exit_reason == SellType.TRAILING_STOP_LOSS.value
@pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [
@ -3782,7 +3782,7 @@ def test_trailing_stop_loss_positive(
f"initial stoploss was at {'2.42' if is_short else '1.80'}0000, "
f"trade opened at {2.2 if is_short else 2.0}00000",
caplog)
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
assert trade.exit_reason == SellType.TRAILING_STOP_LOSS.value
@pytest.mark.parametrize("is_short", [False, True])
@ -3824,7 +3824,7 @@ def test_disable_ignore_roi_if_enter_signal(default_conf_usdt, limit_order, limi
# Test if buy-signal is absent
patch_get_signal(freqtrade)
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.ROI.value
assert trade.exit_reason == SellType.ROI.value
def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog,
@ -4920,7 +4920,7 @@ def test_update_funding_fees(
trade=trade,
# The values of the next 2 params are irrelevant for this test
limit=ticker_usdt_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
exit_reason=SellCheckTuple(sell_type=SellType.ROI)
)
assert trade.funding_fees == pytest.approx(sum(
trade.amount *

View File

@ -111,15 +111,15 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
assert wallets_mock.call_count == 4
trade = trades[0]
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
assert trade.exit_reason == SellType.STOPLOSS_ON_EXCHANGE.value
assert not trade.is_open
trade = trades[1]
assert not trade.sell_reason
assert not trade.exit_reason
assert trade.is_open
trade = trades[2]
assert trade.sell_reason == SellType.SELL_SIGNAL.value
assert trade.exit_reason == SellType.SELL_SIGNAL.value
assert not trade.is_open

View File

@ -1175,7 +1175,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
stop_loss FLOAT,
initial_stop_loss FLOAT,
max_rate FLOAT,
sell_reason VARCHAR,
exit_reason VARCHAR,
strategy VARCHAR,
ticker_interval INTEGER,
stoploss_order_id VARCHAR,
@ -1228,7 +1228,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.min_rate is None
assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0
assert trade.sell_reason is None
assert trade.exit_reason is None
assert trade.strategy is None
assert trade.timeframe == '5m'
assert trade.stoploss_order_id == 'stop_order_id222'
@ -1587,7 +1587,7 @@ def test_to_json(default_conf, fee):
'profit_ratio': None,
'profit_pct': None,
'profit_abs': None,
'sell_reason': None,
'exit_reason': None,
'exit_order_status': None,
'stop_loss_abs': None,
'stop_loss_ratio': None,
@ -1672,7 +1672,7 @@ def test_to_json(default_conf, fee):
'open_order_id': None,
'open_rate_requested': None,
'open_trade_value': 12.33075,
'sell_reason': None,
'exit_reason': None,
'exit_order_status': None,
'strategy': None,
'buy_tag': 'buys_signal_001',
@ -2126,7 +2126,7 @@ def test_Trade_object_idem():
'get_open_trades_without_assigned_fees',
'get_open_order_trades',
'get_trades',
'get_sell_reason_performance',
'get_exit_reason_performance',
'get_enter_tag_performance',
'get_mix_tag_performance',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long