sell_reason -> exit_reason
This commit is contained in:
parent
c899eabe1d
commit
f5805543ed
@ -56,7 +56,7 @@ Currently, the arguments are:
|
|||||||
|
|
||||||
* `results`: DataFrame containing the resulting trades.
|
* `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`):
|
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)`)
|
* `trade_count`: Amount of trades (identical to `len(results)`)
|
||||||
* `min_date`: Start date of the timerange used
|
* `min_date`: Start date of the timerange used
|
||||||
* `min_date`: End date of the timerange used
|
* `min_date`: End date of the timerange used
|
||||||
|
@ -65,7 +65,7 @@ SET is_open=0,
|
|||||||
close_rate=<close_rate>,
|
close_rate=<close_rate>,
|
||||||
close_profit = close_rate / open_rate - 1,
|
close_profit = close_rate / open_rate - 1,
|
||||||
close_profit_abs = (amount * <close_rate> * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))),
|
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>;
|
WHERE id=<trade_ID_to_update>;
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ SET is_open=0,
|
|||||||
close_rate=0.19638016,
|
close_rate=0.19638016,
|
||||||
close_profit=0.0496,
|
close_profit=0.0496,
|
||||||
close_profit_abs = (amount * 0.19638016 * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))),
|
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;
|
WHERE id=31;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ from freqtrade.exchange import timeframe_to_prev_date
|
|||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
|
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:
|
current_time: 'datetime', **kwargs) -> bool:
|
||||||
# Obtain pair dataframe.
|
# Obtain pair dataframe.
|
||||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
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.
|
The provided exit-tag is then used as sell-reason - and shown as such in backtest results.
|
||||||
|
|
||||||
!!! Note
|
!!! 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
|
## Strategy version
|
||||||
|
|
||||||
|
@ -539,7 +539,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Called right before placing a regular sell order.
|
Called right before placing a regular sell order.
|
||||||
@ -555,7 +555,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param amount: Amount in quote currency.
|
:param amount: Amount in quote currency.
|
||||||
:param rate: Rate that's going to be used when using limit orders
|
: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 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',
|
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
||||||
'sell_signal', 'force_sell', 'emergency_sell']
|
'sell_signal', 'force_sell', 'emergency_sell']
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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.
|
:return bool: When True is returned, then the sell-order is placed on the exchange.
|
||||||
False aborts the process
|
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
|
# Reject force-sells with negative profit
|
||||||
# This is just a sample, please adjust to your needs
|
# This is just a sample, please adjust to your needs
|
||||||
# (this does not necessarily make sense, assuming you know when you're force-selling)
|
# (this does not necessarily make sense, assuming you know when you're force-selling)
|
||||||
|
@ -822,7 +822,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati
|
|||||||
!!! Note
|
!!! Note
|
||||||
Providing invalid input to `stoploss_from_open()` may produce "CustomStoploss function did not return valid stoploss" warnings.
|
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
|
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
|
`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`.
|
`current_profit < open_relative_stop`.
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ print(stats['strategy_comparison'])
|
|||||||
trades = load_backtest_data(backtest_dir)
|
trades = load_backtest_data(backtest_dir)
|
||||||
|
|
||||||
# Show value-counts per pair
|
# Show value-counts per pair
|
||||||
trades.groupby("pair")["sell_reason"].value_counts()
|
trades.groupby("pair")["exit_reason"].value_counts()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Plotting daily profit / equity line
|
## 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")
|
trades = load_trades_from_db("sqlite:///tradesv3.sqlite")
|
||||||
|
|
||||||
# Display results
|
# Display results
|
||||||
trades.groupby("pair")["sell_reason"].value_counts()
|
trades.groupby("pair")["exit_reason"].value_counts()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Analyze the loaded trades for trade parallelism
|
## Analyze the loaded trades for trade parallelism
|
||||||
|
@ -178,7 +178,7 @@ Possible parameters are:
|
|||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `sell_reason`
|
* `exit_reason`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
@ -203,7 +203,7 @@ Possible parameters are:
|
|||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `sell_reason`
|
* `exit_reason`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
@ -228,7 +228,7 @@ Possible parameters are:
|
|||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `sell_reason`
|
* `exit_reason`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
|
@ -17,18 +17,18 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# Old format - maybe remove?
|
# Old format - maybe remove?
|
||||||
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
|
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
|
# Mid-term format, created by BacktestResult Named Tuple
|
||||||
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
|
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']
|
'fee_close', 'amount', 'profit_abs', 'profit_ratio']
|
||||||
|
|
||||||
# Newest format
|
# Newest format
|
||||||
BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
|
BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
|
||||||
'open_rate', 'close_rate',
|
'open_rate', 'close_rate',
|
||||||
'fee_open', 'fee_close', 'trade_duration',
|
'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',
|
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
|
||||||
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'enter_tag',
|
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'enter_tag',
|
||||||
'is_short'
|
'is_short'
|
||||||
|
@ -914,7 +914,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
logger.warning('Exiting the trade forcefully')
|
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))
|
sell_type=SellType.EMERGENCY_SELL))
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
@ -947,7 +947,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# We check if stoploss order is fulfilled
|
# We check if stoploss order is fulfilled
|
||||||
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
||||||
# TODO-lev: Update to exit reason
|
# 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,
|
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
||||||
stoploss_order=True)
|
stoploss_order=True)
|
||||||
# Lock pair for one candle to prevent immediate rebuys
|
# Lock pair for one candle to prevent immediate rebuys
|
||||||
@ -1102,7 +1102,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
try:
|
try:
|
||||||
self.execute_trade_exit(
|
self.execute_trade_exit(
|
||||||
trade, order.get('price'),
|
trade, order.get('price'),
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
|
exit_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
||||||
@ -1266,7 +1266,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self,
|
self,
|
||||||
trade: Trade,
|
trade: Trade,
|
||||||
limit: float,
|
limit: float,
|
||||||
sell_reason: SellCheckTuple,
|
exit_reason: SellCheckTuple,
|
||||||
*,
|
*,
|
||||||
exit_tag: Optional[str] = None,
|
exit_tag: Optional[str] = None,
|
||||||
ordertype: Optional[str] = None,
|
ordertype: Optional[str] = None,
|
||||||
@ -1275,7 +1275,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
Executes a trade exit for the given trade and limit
|
Executes a trade exit for the given trade and limit
|
||||||
:param trade: Trade instance
|
:param trade: Trade instance
|
||||||
:param limit: limit rate for the sell order
|
: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)
|
:return: True if it succeeds (supported) False (not supported)
|
||||||
"""
|
"""
|
||||||
trade.funding_fees = self.exchange.get_funding_fees(
|
trade.funding_fees = self.exchange.get_funding_fees(
|
||||||
@ -1284,7 +1284,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.open_date
|
trade.open_date
|
||||||
)
|
)
|
||||||
exit_type = 'sell' # TODO-lev: Update to exit
|
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'
|
exit_type = 'stoploss'
|
||||||
|
|
||||||
# if stoploss is on exchange and we are on dry_run mode,
|
# 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}")
|
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
|
||||||
|
|
||||||
order_type = ordertype or self.strategy.order_types[exit_type]
|
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!)
|
# Emergency sells (default to market!)
|
||||||
order_type = self.strategy.order_types.get("emergencysell", "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)(
|
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,
|
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
|
current_time=datetime.now(timezone.utc)): # TODO-lev: Update to exit
|
||||||
logger.info(f"User requested abortion of exiting {trade.pair}")
|
logger.info(f"User requested abortion of exiting {trade.pair}")
|
||||||
return False
|
return False
|
||||||
@ -1350,7 +1350,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.open_order_id = order['id']
|
trade.open_order_id = order['id']
|
||||||
trade.exit_order_status = ''
|
trade.exit_order_status = ''
|
||||||
trade.close_rate_requested = limit
|
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
|
# Lock pair for one candle to prevent immediate re-trading
|
||||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||||
@ -1395,7 +1395,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
'profit_ratio': profit_ratio,
|
'profit_ratio': profit_ratio,
|
||||||
'buy_tag': trade.enter_tag,
|
'buy_tag': trade.enter_tag,
|
||||||
'enter_tag': trade.enter_tag,
|
'enter_tag': trade.enter_tag,
|
||||||
'sell_reason': trade.sell_reason,
|
'exit_reason': trade.exit_reason,
|
||||||
'open_date': trade.open_date,
|
'open_date': trade.open_date,
|
||||||
'close_date': trade.close_date or datetime.utcnow(),
|
'close_date': trade.close_date or datetime.utcnow(),
|
||||||
'stake_currency': self.config['stake_currency'],
|
'stake_currency': self.config['stake_currency'],
|
||||||
@ -1442,7 +1442,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
'profit_ratio': profit_ratio,
|
'profit_ratio': profit_ratio,
|
||||||
'buy_tag': trade.enter_tag,
|
'buy_tag': trade.enter_tag,
|
||||||
'enter_tag': trade.enter_tag,
|
'enter_tag': trade.enter_tag,
|
||||||
'sell_reason': trade.sell_reason,
|
'exit_reason': trade.exit_reason,
|
||||||
'open_date': trade.open_date,
|
'open_date': trade.open_date,
|
||||||
'close_date': trade.close_date or datetime.now(timezone.utc),
|
'close_date': trade.close_date or datetime.now(timezone.utc),
|
||||||
'stake_currency': self.config['stake_currency'],
|
'stake_currency': self.config['stake_currency'],
|
||||||
|
@ -411,11 +411,11 @@ class Backtesting:
|
|||||||
pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount,
|
pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount,
|
||||||
rate=closerate,
|
rate=closerate,
|
||||||
time_in_force=time_in_force,
|
time_in_force=time_in_force,
|
||||||
sell_reason=sell.sell_reason,
|
exit_reason=sell.exit_reason,
|
||||||
current_time=sell_candle_time):
|
current_time=sell_candle_time):
|
||||||
return None
|
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
|
# Checks and adds an exit tag, after checking that the length of the
|
||||||
# sell_row has the length for an exit tag column
|
# 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 sell_row[EXIT_TAG_IDX] is not None
|
||||||
and len(sell_row[EXIT_TAG_IDX]) > 0
|
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)
|
trade.close(closerate, show_msg=False)
|
||||||
return trade
|
return trade
|
||||||
@ -542,7 +542,7 @@ class Backtesting:
|
|||||||
sell_row = data[pair][-1]
|
sell_row = data[pair][-1]
|
||||||
|
|
||||||
trade.close_date = sell_row[DATE_IDX].to_pydatetime()
|
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)
|
trade.close(sell_row[OPEN_IDX], show_msg=False)
|
||||||
LocalTrade.close_bt_trade(trade)
|
LocalTrade.close_bt_trade(trade)
|
||||||
# Deepcopy object to have wallets update correctly
|
# Deepcopy object to have wallets update correctly
|
||||||
|
@ -159,7 +159,7 @@ def generate_tag_metrics(tag_type: str,
|
|||||||
return []
|
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
|
Generate small table outlining Backtest results
|
||||||
:param max_open_trades: Max_open_trades parameter
|
: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 = []
|
tabular_data = []
|
||||||
|
|
||||||
for reason, count in results['sell_reason'].value_counts().iteritems():
|
for reason, count in results['exit_reason'].value_counts().iteritems():
|
||||||
result = results.loc[results['sell_reason'] == reason]
|
result = results.loc[results['exit_reason'] == reason]
|
||||||
|
|
||||||
profit_mean = result['profit_ratio'].mean()
|
profit_mean = result['profit_ratio'].mean()
|
||||||
profit_sum = result['profit_ratio'].sum()
|
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(
|
tabular_data.append(
|
||||||
{
|
{
|
||||||
'sell_reason': reason,
|
'exit_reason': reason,
|
||||||
'trades': count,
|
'trades': count,
|
||||||
'wins': len(result[result['profit_abs'] > 0]),
|
'wins': len(result[result['profit_abs'] > 0]),
|
||||||
'draws': 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,
|
enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance,
|
||||||
results=results, skip_nan=False)
|
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)
|
results=results)
|
||||||
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
||||||
starting_balance=start_balance,
|
starting_balance=start_balance,
|
||||||
@ -407,7 +407,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
|
|||||||
'worst_pair': worst_pair,
|
'worst_pair': worst_pair,
|
||||||
'results_per_pair': pair_results,
|
'results_per_pair': pair_results,
|
||||||
'results_per_enter_tag': enter_tag_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,
|
'left_open_trades': left_open_results,
|
||||||
# 'days_breakdown_stats': days_breakdown_stats,
|
# '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")
|
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
|
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
|
:param stake_currency: Stakecurrency used
|
||||||
:return: pretty printed table with tabulate as string
|
: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 = [[
|
output = [[
|
||||||
t['sell_reason'], t['trades'],
|
t['exit_reason'], t['trades'],
|
||||||
_generate_wins_draws_losses(t['wins'], t['draws'], t['losses']),
|
_generate_wins_draws_losses(t['wins'], t['draws'], t['losses']),
|
||||||
t['profit_mean_pct'], t['profit_sum_pct'],
|
t['profit_mean_pct'], t['profit_sum_pct'],
|
||||||
round_coin_value(t['profit_total_abs'], stake_currency, False),
|
round_coin_value(t['profit_total_abs'], stake_currency, False),
|
||||||
t['profit_total_pct'],
|
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")
|
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(' BUY TAG STATS '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
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)
|
stake_currency=stake_currency)
|
||||||
if isinstance(table, str) and len(table) > 0:
|
if isinstance(table, str) and len(table) > 0:
|
||||||
print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
|
print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
|
||||||
|
@ -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')
|
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
|
||||||
max_rate = get_column_def(cols, 'max_rate', '0.0')
|
max_rate = get_column_def(cols, 'max_rate', '0.0')
|
||||||
min_rate = get_column_def(cols, 'min_rate', 'null')
|
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')
|
strategy = get_column_def(cols, 'strategy', 'null')
|
||||||
enter_tag = get_column_def(cols, 'buy_tag', get_column_def(cols, 'enter_tag', '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,
|
stake_amount, amount, amount_requested, open_date, close_date, open_order_id,
|
||||||
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
||||||
stoploss_order_id, stoploss_last_update,
|
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,
|
timeframe, open_trade_value, close_profit_abs,
|
||||||
trading_mode, leverage, isolated_liq, is_short,
|
trading_mode, leverage, isolated_liq, is_short,
|
||||||
interest_rate, funding_fees
|
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} initial_stop_loss,
|
||||||
{initial_stop_loss_pct} initial_stop_loss_pct,
|
{initial_stop_loss_pct} initial_stop_loss_pct,
|
||||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
{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,
|
{exit_order_status} exit_order_status,
|
||||||
{strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
|
{strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
|
||||||
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
||||||
|
@ -261,7 +261,7 @@ class LocalTrade():
|
|||||||
max_rate: float = 0.0
|
max_rate: float = 0.0
|
||||||
# Lowest price reached
|
# Lowest price reached
|
||||||
min_rate: float = 0.0
|
min_rate: float = 0.0
|
||||||
sell_reason: str = ''
|
exit_reason: str = ''
|
||||||
exit_order_status: str = ''
|
exit_order_status: str = ''
|
||||||
strategy: str = ''
|
strategy: str = ''
|
||||||
enter_tag: Optional[str] = None
|
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_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
|
||||||
'profit_abs': self.close_profit_abs,
|
'profit_abs': self.close_profit_abs,
|
||||||
|
|
||||||
'sell_reason': self.sell_reason,
|
'exit_reason': self.exit_reason,
|
||||||
'exit_order_status': self.exit_order_status,
|
'exit_order_status': self.exit_order_status,
|
||||||
'stop_loss_abs': self.stop_loss,
|
'stop_loss_abs': self.stop_loss,
|
||||||
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
|
'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'):
|
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
|
||||||
self.stoploss_order_id = None
|
self.stoploss_order_id = None
|
||||||
self.close_rate_requested = self.stop_loss
|
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:
|
if self.is_open:
|
||||||
logger.info(f'{order_type.upper()} is hit for {self}.')
|
logger.info(f'{order_type.upper()} is hit for {self}.')
|
||||||
self.close(safe_value_fallback(order, 'average', 'price'))
|
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)
|
max_rate = Column(Float, nullable=True, default=0.0)
|
||||||
# Lowest price reached
|
# Lowest price reached
|
||||||
min_rate = Column(Float, nullable=True)
|
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)
|
exit_order_status = Column(String(100), nullable=True)
|
||||||
strategy = Column(String(100), nullable=True)
|
strategy = Column(String(100), nullable=True)
|
||||||
enter_tag = Column(String(100), nullable=True)
|
enter_tag = Column(String(100), nullable=True)
|
||||||
@ -1143,7 +1143,7 @@ class Trade(_DECL_BASE, LocalTrade):
|
|||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@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
|
Returns List of dicts containing all Trades, based on sell reason performance
|
||||||
Can either be average for all pairs or a specific pair provided
|
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)
|
filters.append(Trade.pair == pair)
|
||||||
|
|
||||||
sell_tag_perf = Trade.query.with_entities(
|
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).label('profit_sum'),
|
||||||
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
||||||
func.count(Trade.pair).label('count')
|
func.count(Trade.pair).label('count')
|
||||||
).filter(*filters)\
|
).filter(*filters)\
|
||||||
.group_by(Trade.sell_reason) \
|
.group_by(Trade.exit_reason) \
|
||||||
.order_by(desc('profit_sum_abs')) \
|
.order_by(desc('profit_sum_abs')) \
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
return [
|
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_ratio': profit,
|
||||||
'profit_pct': round(profit * 100, 2),
|
'profit_pct': round(profit * 100, 2),
|
||||||
'profit_abs': profit_abs,
|
'profit_abs': profit_abs,
|
||||||
'count': count
|
'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
|
@staticmethod
|
||||||
def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]:
|
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
|
Can either be average for all pairs or a specific pair provided
|
||||||
NOTE: Not supported in Backtesting.
|
NOTE: Not supported in Backtesting.
|
||||||
"""
|
"""
|
||||||
@ -1190,7 +1190,7 @@ class Trade(_DECL_BASE, LocalTrade):
|
|||||||
mix_tag_perf = Trade.query.with_entities(
|
mix_tag_perf = Trade.query.with_entities(
|
||||||
Trade.id,
|
Trade.id,
|
||||||
Trade.enter_tag,
|
Trade.enter_tag,
|
||||||
Trade.sell_reason,
|
Trade.exit_reason,
|
||||||
func.sum(Trade.close_profit).label('profit_sum'),
|
func.sum(Trade.close_profit).label('profit_sum'),
|
||||||
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
||||||
func.count(Trade.pair).label('count')
|
func.count(Trade.pair).label('count')
|
||||||
@ -1200,12 +1200,12 @@ class Trade(_DECL_BASE, LocalTrade):
|
|||||||
.all()
|
.all()
|
||||||
|
|
||||||
return_list: List[Dict] = []
|
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"
|
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):
|
if(exit_reason is not None and enter_tag is not None):
|
||||||
mix_tag = enter_tag + " " + sell_reason
|
mix_tag = enter_tag + " " + exit_reason
|
||||||
i = 0
|
i = 0
|
||||||
if not any(item["mix_tag"] == mix_tag for item in return_list):
|
if not any(item["mix_tag"] == mix_tag for item in return_list):
|
||||||
return_list.append({'mix_tag': mix_tag,
|
return_list.append({'mix_tag': mix_tag,
|
||||||
|
@ -236,7 +236,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
|
|||||||
if trades is not None and len(trades) > 0:
|
if trades is not None and len(trades) > 0:
|
||||||
# Create description for sell summarizing the trade
|
# Create description for sell summarizing the trade
|
||||||
trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, "
|
trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, "
|
||||||
f"{row['sell_reason']}, "
|
f"{row['exit_reason']}, "
|
||||||
f"{row['trade_duration']} min",
|
f"{row['trade_duration']} min",
|
||||||
axis=1)
|
axis=1)
|
||||||
trade_buys = go.Scatter(
|
trade_buys = go.Scatter(
|
||||||
@ -535,7 +535,7 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
|
|||||||
"Profit per pair",
|
"Profit per pair",
|
||||||
"Parallelism",
|
"Parallelism",
|
||||||
"Underwater",
|
"Underwater",
|
||||||
])
|
])
|
||||||
fig['layout'].update(title="Freqtrade Profit plot")
|
fig['layout'].update(title="Freqtrade Profit plot")
|
||||||
fig['layout']['yaxis1'].update(title='Price')
|
fig['layout']['yaxis1'].update(title='Price')
|
||||||
fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}')
|
fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}')
|
||||||
|
@ -44,8 +44,8 @@ class StoplossGuard(IProtection):
|
|||||||
# filters = [
|
# filters = [
|
||||||
# Trade.is_open.is_(False),
|
# Trade.is_open.is_(False),
|
||||||
# Trade.close_date > look_back_until,
|
# Trade.close_date > look_back_until,
|
||||||
# or_(Trade.sell_reason == SellType.STOP_LOSS.value,
|
# or_(Trade.exit_reason == SellType.STOP_LOSS.value,
|
||||||
# and_(Trade.sell_reason == SellType.TRAILING_STOP_LOSS.value,
|
# and_(Trade.exit_reason == SellType.TRAILING_STOP_LOSS.value,
|
||||||
# Trade.close_profit < 0))
|
# Trade.close_profit < 0))
|
||||||
# ]
|
# ]
|
||||||
# if pair:
|
# if pair:
|
||||||
@ -54,7 +54,7 @@ class StoplossGuard(IProtection):
|
|||||||
# TODO-lev: Liquidation price?
|
# TODO-lev: Liquidation price?
|
||||||
|
|
||||||
trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until)
|
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.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value,
|
||||||
SellType.STOPLOSS_ON_EXCHANGE.value)
|
SellType.STOPLOSS_ON_EXCHANGE.value)
|
||||||
and trade.close_profit and trade.close_profit < 0)]
|
and trade.close_profit and trade.close_profit < 0)]
|
||||||
|
@ -108,7 +108,7 @@ class SellReason(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Stats(BaseModel):
|
class Stats(BaseModel):
|
||||||
sell_reasons: Dict[str, SellReason]
|
exit_reasons: Dict[str, SellReason]
|
||||||
durations: Dict[str, Union[str, float]]
|
durations: Dict[str, Union[str, float]]
|
||||||
|
|
||||||
|
|
||||||
@ -212,7 +212,7 @@ class TradeSchema(BaseModel):
|
|||||||
profit_pct: Optional[float]
|
profit_pct: Optional[float]
|
||||||
profit_abs: Optional[float]
|
profit_abs: Optional[float]
|
||||||
profit_fiat: Optional[float]
|
profit_fiat: Optional[float]
|
||||||
sell_reason: Optional[str]
|
exit_reason: Optional[str]
|
||||||
exit_order_status: Optional[str]
|
exit_order_status: Optional[str]
|
||||||
stop_loss_abs: Optional[float]
|
stop_loss_abs: Optional[float]
|
||||||
stop_loss_ratio: Optional[float]
|
stop_loss_ratio: Optional[float]
|
||||||
|
@ -416,11 +416,11 @@ class RPC:
|
|||||||
return 'draws'
|
return 'draws'
|
||||||
trades = trades = Trade.get_trades([Trade.is_open.is_(False)])
|
trades = trades = Trade.get_trades([Trade.is_open.is_(False)])
|
||||||
# Sell reason
|
# Sell reason
|
||||||
sell_reasons = {}
|
exit_reasons = {}
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
if trade.sell_reason not in sell_reasons:
|
if trade.exit_reason not in exit_reasons:
|
||||||
sell_reasons[trade.sell_reason] = {'wins': 0, 'losses': 0, 'draws': 0}
|
exit_reasons[trade.exit_reason] = {'wins': 0, 'losses': 0, 'draws': 0}
|
||||||
sell_reasons[trade.sell_reason][trade_win_loss(trade)] += 1
|
exit_reasons[trade.exit_reason][trade_win_loss(trade)] += 1
|
||||||
|
|
||||||
# Duration
|
# Duration
|
||||||
dur: Dict[str, List[int]] = {'wins': [], 'draws': [], 'losses': []}
|
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'
|
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}
|
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(
|
def _rpc_trade_statistics(
|
||||||
self, stake_currency: str, fiat_display_currency: str,
|
self, stake_currency: str, fiat_display_currency: str,
|
||||||
@ -672,12 +672,12 @@ class RPC:
|
|||||||
closing_side = "buy" if trade.is_short else "sell"
|
closing_side = "buy" if trade.is_short else "sell"
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
trade.pair, refresh=False, side=closing_side)
|
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(
|
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
||||||
"forcesell", self._freqtrade.strategy.order_types["sell"])
|
"forcesell", self._freqtrade.strategy.order_types["sell"])
|
||||||
|
|
||||||
self._freqtrade.execute_trade_exit(
|
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 ----
|
# ---- EOF def _exec_forcesell ----
|
||||||
|
|
||||||
if self._freqtrade.state != State.RUNNING:
|
if self._freqtrade.state != State.RUNNING:
|
||||||
@ -799,16 +799,16 @@ class RPC:
|
|||||||
"""
|
"""
|
||||||
return Trade.get_enter_tag_performance(pair)
|
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.
|
Handler for sell reason performance.
|
||||||
Shows a performance statistic from finished trades
|
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]]:
|
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
|
Shows a performance statistic from finished trades
|
||||||
"""
|
"""
|
||||||
mix_tags = Trade.get_mix_tag_performance(pair)
|
mix_tags = Trade.get_mix_tag_performance(pair)
|
||||||
@ -872,7 +872,7 @@ class RPC:
|
|||||||
else:
|
else:
|
||||||
errors[pair] = {
|
errors[pair] = {
|
||||||
'error_msg': f"Pair {pair} is not in the current blacklist."
|
'error_msg': f"Pair {pair} is not in the current blacklist."
|
||||||
}
|
}
|
||||||
resp = self._rpc_blacklist()
|
resp = self._rpc_blacklist()
|
||||||
resp['errors'] = errors
|
resp['errors'] = errors
|
||||||
return resp
|
return resp
|
||||||
|
@ -156,7 +156,7 @@ class Telegram(RPCHandler):
|
|||||||
CommandHandler('delete', self._delete_trade),
|
CommandHandler('delete', self._delete_trade),
|
||||||
CommandHandler('performance', self._performance),
|
CommandHandler('performance', self._performance),
|
||||||
CommandHandler(['buys', 'entries'], self._enter_tag_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('mix_tags', self._mix_tag_performance),
|
||||||
CommandHandler('stats', self._stats),
|
CommandHandler('stats', self._stats),
|
||||||
CommandHandler('daily', self._daily),
|
CommandHandler('daily', self._daily),
|
||||||
@ -186,8 +186,8 @@ class Telegram(RPCHandler):
|
|||||||
CallbackQueryHandler(self._performance, pattern='update_performance'),
|
CallbackQueryHandler(self._performance, pattern='update_performance'),
|
||||||
CallbackQueryHandler(self._enter_tag_performance,
|
CallbackQueryHandler(self._enter_tag_performance,
|
||||||
pattern='update_enter_tag_performance'),
|
pattern='update_enter_tag_performance'),
|
||||||
CallbackQueryHandler(self._sell_reason_performance,
|
CallbackQueryHandler(self._exit_reason_performance,
|
||||||
pattern='update_sell_reason_performance'),
|
pattern='update_exit_reason_performance'),
|
||||||
CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'),
|
CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'),
|
||||||
CallbackQueryHandler(self._count, pattern='update_count'),
|
CallbackQueryHandler(self._count, pattern='update_count'),
|
||||||
CallbackQueryHandler(self._forcebuy_inline),
|
CallbackQueryHandler(self._forcebuy_inline),
|
||||||
@ -284,7 +284,7 @@ class Telegram(RPCHandler):
|
|||||||
f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
|
f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
|
||||||
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
|
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
|
||||||
f"*Enter Tag:* `{msg['enter_tag']}`\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"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n"
|
||||||
f"*Direction:* `{msg['direction']}`\n"
|
f"*Direction:* `{msg['direction']}`\n"
|
||||||
f"{msg['leverage_text']}"
|
f"{msg['leverage_text']}"
|
||||||
@ -355,7 +355,7 @@ class Telegram(RPCHandler):
|
|||||||
if isinstance(sell_noti, str):
|
if isinstance(sell_noti, str):
|
||||||
noti = sell_noti
|
noti = sell_noti
|
||||||
else:
|
else:
|
||||||
noti = sell_noti.get(str(msg['sell_reason']), default_noti)
|
noti = sell_noti.get(str(msg['exit_reason']), default_noti)
|
||||||
else:
|
else:
|
||||||
noti = self._config['telegram'] \
|
noti = self._config['telegram'] \
|
||||||
.get('notification_settings', {}).get(str(msg_type), default_noti)
|
.get('notification_settings', {}).get(str(msg_type), default_noti)
|
||||||
@ -378,7 +378,7 @@ class Telegram(RPCHandler):
|
|||||||
return "\N{ROCKET}"
|
return "\N{ROCKET}"
|
||||||
elif float(msg['profit_percent']) >= 0.0:
|
elif float(msg['profit_percent']) >= 0.0:
|
||||||
return "\N{EIGHT SPOKED ASTERISK}"
|
return "\N{EIGHT SPOKED ASTERISK}"
|
||||||
elif msg['sell_reason'] == "stop_loss":
|
elif msg['exit_reason'] == "stop_loss":
|
||||||
return "\N{WARNING SIGN}"
|
return "\N{WARNING SIGN}"
|
||||||
else:
|
else:
|
||||||
return "\N{CROSS MARK}"
|
return "\N{CROSS MARK}"
|
||||||
@ -697,23 +697,23 @@ class Telegram(RPCHandler):
|
|||||||
'force_sell': 'Forcesell',
|
'force_sell': 'Forcesell',
|
||||||
'emergency_sell': 'Emergency Sell',
|
'emergency_sell': 'Emergency Sell',
|
||||||
}
|
}
|
||||||
sell_reasons_tabulate = [
|
exit_reasons_tabulate = [
|
||||||
[
|
[
|
||||||
reason_map.get(reason, reason),
|
reason_map.get(reason, reason),
|
||||||
sum(count.values()),
|
sum(count.values()),
|
||||||
count['wins'],
|
count['wins'],
|
||||||
count['losses']
|
count['losses']
|
||||||
] for reason, count in stats['sell_reasons'].items()
|
] for reason, count in stats['exit_reasons'].items()
|
||||||
]
|
]
|
||||||
sell_reasons_msg = 'No trades yet.'
|
exit_reasons_msg = 'No trades yet.'
|
||||||
for reason in chunks(sell_reasons_tabulate, 25):
|
for reason in chunks(exit_reasons_tabulate, 25):
|
||||||
sell_reasons_msg = tabulate(
|
exit_reasons_msg = tabulate(
|
||||||
reason,
|
reason,
|
||||||
headers=['Sell Reason', 'Sells', 'Wins', 'Losses']
|
headers=['Sell Reason', 'Sells', 'Wins', 'Losses']
|
||||||
)
|
)
|
||||||
if len(sell_reasons_tabulate) > 25:
|
if len(exit_reasons_tabulate) > 25:
|
||||||
self._send_msg(sell_reasons_msg, ParseMode.MARKDOWN)
|
self._send_msg(exit_reasons_msg, ParseMode.MARKDOWN)
|
||||||
sell_reasons_msg = ''
|
exit_reasons_msg = ''
|
||||||
|
|
||||||
durations = stats['durations']
|
durations = stats['durations']
|
||||||
duration_msg = tabulate(
|
duration_msg = tabulate(
|
||||||
@ -725,7 +725,7 @@ class Telegram(RPCHandler):
|
|||||||
],
|
],
|
||||||
headers=['', 'Avg. Duration']
|
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)
|
self._send_msg(msg, ParseMode.MARKDOWN)
|
||||||
|
|
||||||
@ -1026,7 +1026,7 @@ class Telegram(RPCHandler):
|
|||||||
self._send_msg(str(e))
|
self._send_msg(str(e))
|
||||||
|
|
||||||
@authorized_only
|
@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.
|
Handler for /sells.
|
||||||
Shows a performance statistic from finished trades
|
Shows a performance statistic from finished trades
|
||||||
@ -1039,11 +1039,11 @@ class Telegram(RPCHandler):
|
|||||||
if context.args and isinstance(context.args[0], str):
|
if context.args and isinstance(context.args[0], str):
|
||||||
pair = context.args[0]
|
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"
|
output = "<b>Sell Reason Performance:</b>\n"
|
||||||
for i, trade in enumerate(trades):
|
for i, trade in enumerate(trades):
|
||||||
stat_line = (
|
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"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "
|
||||||
f"({trade['profit_ratio']:.2%}) "
|
f"({trade['profit_ratio']:.2%}) "
|
||||||
f"({trade['count']})</code>\n")
|
f"({trade['count']})</code>\n")
|
||||||
@ -1055,7 +1055,7 @@ class Telegram(RPCHandler):
|
|||||||
output += stat_line
|
output += stat_line
|
||||||
|
|
||||||
self._send_msg(output, parse_mode=ParseMode.HTML,
|
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)
|
query=update.callback_query)
|
||||||
except RPCException as e:
|
except RPCException as e:
|
||||||
self._send_msg(str(e))
|
self._send_msg(str(e))
|
||||||
|
@ -35,11 +35,11 @@ class SellCheckTuple:
|
|||||||
NamedTuple for Sell type + reason
|
NamedTuple for Sell type + reason
|
||||||
"""
|
"""
|
||||||
sell_type: SellType
|
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_type = sell_type
|
||||||
self.sell_reason = sell_reason or sell_type.value
|
self.exit_reason = exit_reason or sell_type.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sell_flag(self):
|
def sell_flag(self):
|
||||||
@ -256,7 +256,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
|
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:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Called right before placing a regular exit order.
|
Called right before placing a regular exit order.
|
||||||
@ -273,7 +273,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
:param amount: Amount in quote currency.
|
:param amount: Amount in quote currency.
|
||||||
:param rate: Rate that's going to be used when using limit orders
|
: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 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',
|
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
||||||
'sell_signal', 'force_sell', 'emergency_sell']
|
'sell_signal', 'force_sell', 'emergency_sell']
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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. "
|
logger.debug(f"{trade.pair} - Sell signal received. "
|
||||||
f"sell_type=SellType.{sell_signal.name}" +
|
f"sell_type=SellType.{sell_signal.name}" +
|
||||||
(f", custom_reason={custom_reason}" if custom_reason else ""))
|
(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:
|
# Sequence:
|
||||||
# Exit-signal
|
# Exit-signal
|
||||||
|
@ -187,7 +187,7 @@
|
|||||||
"trades = load_backtest_data(backtest_dir)\n",
|
"trades = load_backtest_data(backtest_dir)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Show value-counts per pair\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",
|
"trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Display results\n",
|
"# Display results\n",
|
||||||
"trades.groupby(\"pair\")[\"sell_reason\"].value_counts()"
|
"trades.groupby(\"pair\")[\"exit_reason\"].value_counts()"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -103,7 +103,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
|
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:
|
current_time: 'datetime', **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Called right before placing a regular sell order.
|
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 amount: Amount in quote currency.
|
||||||
:param rate: Rate that's going to be used when using limit orders
|
: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 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',
|
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
||||||
'sell_signal', 'force_sell', 'emergency_sell']
|
'sell_signal', 'force_sell', 'emergency_sell']
|
||||||
:param current_time: datetime object, containing the current datetime
|
:param current_time: datetime object, containing the current datetime
|
||||||
|
@ -103,7 +103,7 @@ def mock_trade_2(fee, is_short: bool):
|
|||||||
strategy='StrategyTestV3',
|
strategy='StrategyTestV3',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
enter_tag='TEST1',
|
enter_tag='TEST1',
|
||||||
sell_reason='sell_signal',
|
exit_reason='sell_signal',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
||||||
is_short=is_short
|
is_short=is_short
|
||||||
@ -163,7 +163,7 @@ def mock_trade_3(fee, is_short: bool):
|
|||||||
is_open=False,
|
is_open=False,
|
||||||
strategy='StrategyTestV3',
|
strategy='StrategyTestV3',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
sell_reason='roi',
|
exit_reason='roi',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
close_date=datetime.now(tz=timezone.utc),
|
close_date=datetime.now(tz=timezone.utc),
|
||||||
is_short=is_short
|
is_short=is_short
|
||||||
@ -400,7 +400,7 @@ def short_trade(fee):
|
|||||||
open_order_id='dry_run_exit_short_12345',
|
open_order_id='dry_run_exit_short_12345',
|
||||||
strategy='DefaultStrategy',
|
strategy='DefaultStrategy',
|
||||||
timeframe=5,
|
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),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
# close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
# close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
||||||
is_short=True
|
is_short=True
|
||||||
@ -489,7 +489,7 @@ def leverage_trade(fee):
|
|||||||
open_order_id='dry_run_leverage_buy_12368',
|
open_order_id='dry_run_leverage_buy_12368',
|
||||||
strategy='DefaultStrategy',
|
strategy='DefaultStrategy',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
sell_reason='sell_signal',
|
exit_reason='sell_signal',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300),
|
||||||
close_date=datetime.now(tz=timezone.utc),
|
close_date=datetime.now(tz=timezone.utc),
|
||||||
interest_rate=0.0005
|
interest_rate=0.0005
|
||||||
|
@ -89,7 +89,7 @@ def mock_trade_usdt_2(fee):
|
|||||||
open_order_id='dry_run_sell_12345',
|
open_order_id='dry_run_sell_12345',
|
||||||
strategy='StrategyTestV2',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
sell_reason='sell_signal',
|
exit_reason='sell_signal',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
||||||
)
|
)
|
||||||
@ -148,7 +148,7 @@ def mock_trade_usdt_3(fee):
|
|||||||
is_open=False,
|
is_open=False,
|
||||||
strategy='StrategyTestV2',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
sell_reason='roi',
|
exit_reason='roi',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
close_date=datetime.now(tz=timezone.utc),
|
close_date=datetime.now(tz=timezone.utc),
|
||||||
)
|
)
|
||||||
|
@ -95,8 +95,8 @@ tc1 = BTContainer(data=[
|
|||||||
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
|
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
|
||||||
],
|
],
|
||||||
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
|
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),
|
trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
|
||||||
BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
|
BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
# 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],
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||||
],
|
],
|
||||||
stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01,
|
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
|
# 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],
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||||
],
|
],
|
||||||
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
|
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
|
# 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],
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||||
],
|
],
|
||||||
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
|
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 = [
|
TESTS = [
|
||||||
@ -162,7 +162,7 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None:
|
|||||||
|
|
||||||
for c, trade in enumerate(data.trades):
|
for c, trade in enumerate(data.trades):
|
||||||
res = results.iloc[c]
|
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.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)
|
assert res.close_date == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class BTrade(NamedTuple):
|
|||||||
"""
|
"""
|
||||||
Minimalistic Trade result used for functional backtesting
|
Minimalistic Trade result used for functional backtesting
|
||||||
"""
|
"""
|
||||||
sell_reason: SellType
|
exit_reason: SellType
|
||||||
open_tick: int
|
open_tick: int
|
||||||
close_tick: int
|
close_tick: int
|
||||||
enter_tag: Optional[str] = None
|
enter_tag: Optional[str] = None
|
||||||
|
@ -44,7 +44,7 @@ def hyperopt_results():
|
|||||||
'profit_abs': [-0.2, 0.4, -0.2, 0.6],
|
'profit_abs': [-0.2, 0.4, -0.2, 0.6],
|
||||||
'trade_duration': [10, 30, 10, 10],
|
'trade_duration': [10, 30, 10, 10],
|
||||||
'amount': [0.1, 0.1, 0.1, 0.1],
|
'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':
|
'open_date':
|
||||||
[
|
[
|
||||||
datetime(2019, 1, 1, 9, 15, 0),
|
datetime(2019, 1, 1, 9, 15, 0),
|
||||||
|
@ -22,7 +22,7 @@ tc0 = BTContainer(data=[
|
|||||||
[4, 5010, 5011, 4977, 4995, 6172, 0, 0],
|
[4, 5010, 5011, 4977, 4995, 6172, 0, 0],
|
||||||
[5, 4995, 4995, 4950, 4950, 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,
|
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
|
# Test 1: Stop-Loss Triggered 1% loss
|
||||||
@ -36,7 +36,7 @@ tc1 = BTContainer(data=[
|
|||||||
[4, 4977, 4995, 4977, 4995, 6172, 0, 0],
|
[4, 4977, 4995, 4977, 4995, 6172, 0, 0],
|
||||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01,
|
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],
|
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
|
||||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03,
|
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
|
[5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit
|
||||||
[6, 4950, 4975, 4950, 4950, 6172, 0, 0]],
|
[6, 4950, 4975, 4950, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04,
|
stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04,
|
||||||
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),
|
||||||
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
|
BTrade(exit_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 4: Minus 3% / recovery +15%
|
# Test 4: Minus 3% / recovery +15%
|
||||||
@ -88,7 +88,7 @@ tc4 = BTContainer(data=[
|
|||||||
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
|
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
|
||||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02,
|
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
|
# 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],
|
[4, 4962, 4987, 4962, 4972, 6172, 0, 0],
|
||||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03,
|
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
|
# 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],
|
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
|
||||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02,
|
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
|
# 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],
|
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
|
||||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03,
|
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],
|
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||||
[4, 4750, 4950, 4350, 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,
|
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],
|
[3, 5000, 5200, 4550, 4850, 6172, 0, 0],
|
||||||
[4, 4750, 4950, 4350, 4750, 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,
|
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
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.10,
|
||||||
trailing_stop_positive=0.03,
|
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
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||||
trailing_stop_positive=0.03,
|
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
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||||
trailing_stop_positive=0.03,
|
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
|
# Test 13: Buy and sell ROI on same candle
|
||||||
@ -219,7 +219,7 @@ tc13 = BTContainer(data=[
|
|||||||
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
|
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
|
||||||
[4, 4750, 4950, 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,
|
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
|
# Test 14 - Buy and Stoploss on same candle
|
||||||
@ -232,7 +232,7 @@ tc14 = BTContainer(data=[
|
|||||||
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
|
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
|
||||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05,
|
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],
|
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
|
||||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04,
|
stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04,
|
||||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1),
|
trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=1),
|
||||||
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)]
|
BTrade(exit_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 16: Buy, hold for 65 min, then forcesell using roi=-1
|
# 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],
|
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
|
||||||
[5, 4950, 4975, 4925, 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,
|
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
|
# 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],
|
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
|
||||||
[5, 4950, 4975, 4925, 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,
|
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],
|
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
|
||||||
[5, 4950, 4975, 4925, 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,
|
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.
|
# 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],
|
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
|
||||||
[5, 4550, 4975, 4550, 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,
|
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.
|
# 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],
|
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
|
||||||
[5, 4925, 4975, 4925, 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,
|
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.
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||||
trailing_stop_positive=0.03,
|
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.
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||||
trailing_stop_positive=0.03,
|
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)
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||||
trailing_stop_positive=0.03,
|
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)
|
# 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],
|
[4, 5010, 5010, 4977, 4995, 6172, 0, 0],
|
||||||
[5, 4995, 4995, 4950, 4950, 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,
|
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)
|
# 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
|
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
|
||||||
[5, 4995, 4995, 4950, 4950, 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,
|
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)
|
# 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],
|
[4, 5010, 5010, 4855, 4995, 6172, 0, 0],
|
||||||
[5, 4995, 4995, 4950, 4950, 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,
|
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)
|
# 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
|
[4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on
|
||||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
||||||
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_exit_signal=True,
|
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
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||||
trailing_stop_positive=0.03,
|
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
|
# 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]],
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True,
|
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True,
|
||||||
trailing_stop_positive=0.03,
|
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.
|
# 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]],
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
|
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
|
||||||
trailing_stop_positive=0.01,
|
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.
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
|
||||||
trailing_stop_positive=0.01,
|
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.
|
# 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,
|
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_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
|
||||||
trailing_stop_positive=0.01, use_custom_stoploss=True,
|
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.
|
# 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_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
|
||||||
trailing_stop_positive=0.01, use_custom_stoploss=True,
|
trailing_stop_positive=0.01, use_custom_stoploss=True,
|
||||||
trades=[BTrade(
|
trades=[BTrade(
|
||||||
sell_reason=SellType.TRAILING_STOP_LOSS,
|
exit_reason=SellType.TRAILING_STOP_LOSS,
|
||||||
open_tick=1,
|
open_tick=1,
|
||||||
close_tick=1,
|
close_tick=1,
|
||||||
enter_tag='buy_signal_01'
|
enter_tag='buy_signal_01'
|
||||||
@ -548,7 +548,7 @@ tc34 = BTContainer(data=[
|
|||||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
[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,
|
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
|
||||||
leverage=5.0,
|
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 = [
|
TESTS = [
|
||||||
@ -641,7 +641,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
|||||||
|
|
||||||
for c, trade in enumerate(data.trades):
|
for c, trade in enumerate(data.trades):
|
||||||
res = results.iloc[c]
|
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.enter_tag == trade.enter_tag
|
||||||
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
|
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
|
||||||
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)
|
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)
|
||||||
|
@ -628,7 +628,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
|
|||||||
# No data available.
|
# No data available.
|
||||||
res = backtesting._get_sell_trade_entry(trade, row_sell)
|
res = backtesting._get_sell_trade_entry(trade, row_sell)
|
||||||
assert res is not None
|
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)
|
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
|
||||||
|
|
||||||
# Enter new trade
|
# 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)
|
res = backtesting._get_sell_trade_entry(trade, row_sell)
|
||||||
assert res is not None
|
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!)
|
# Sell at minute 3 (not available above!)
|
||||||
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
|
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
|
||||||
assert round(res.close_rate, 3) == round(209.0225, 3)
|
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],
|
'trade_duration': [235, 40],
|
||||||
'profit_ratio': [0.0, 0.0],
|
'profit_ratio': [0.0, 0.0],
|
||||||
'profit_abs': [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_abs': [0.0940005, 0.09272236],
|
||||||
'initial_stop_loss_ratio': [-0.1, -0.1],
|
'initial_stop_loss_ratio': [-0.1, -0.1],
|
||||||
'stop_loss_abs': [0.0940005, 0.09272236],
|
'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']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
||||||
text_table_mock = MagicMock()
|
text_table_mock = MagicMock()
|
||||||
sell_reason_mock = MagicMock()
|
exit_reason_mock = MagicMock()
|
||||||
strattable_mock = MagicMock()
|
strattable_mock = MagicMock()
|
||||||
strat_summary = 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_bt_results=text_table_mock,
|
||||||
text_table_strategy=strattable_mock,
|
text_table_strategy=strattable_mock,
|
||||||
generate_pair_metrics=MagicMock(),
|
generate_pair_metrics=MagicMock(),
|
||||||
generate_sell_reason_stats=sell_reason_mock,
|
generate_exit_reason_stats=exit_reason_mock,
|
||||||
generate_strategy_comparison=strat_summary,
|
generate_strategy_comparison=strat_summary,
|
||||||
generate_daily_stats=MagicMock(),
|
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 backtestmock.call_count == 2
|
||||||
assert text_table_mock.call_count == 4
|
assert text_table_mock.call_count == 4
|
||||||
assert strattable_mock.call_count == 1
|
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
|
assert strat_summary.call_count == 1
|
||||||
|
|
||||||
# check the logs, that will contain the backtest result
|
# 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],
|
'close_rate': [0.104969, 0.103541],
|
||||||
"is_short": [False, False],
|
"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'],
|
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
||||||
'profit_ratio': [0.03, 0.01, 0.1],
|
'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],
|
'open_rate': [0.104445, 0.10302485, 0.122541],
|
||||||
'close_rate': [0.104969, 0.103541, 0.123541],
|
'close_rate': [0.104969, 0.103541, 0.123541],
|
||||||
"is_short": [False, False, False],
|
"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=[
|
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],
|
'stake_amount': [0.01, 0.01],
|
||||||
'open_rate': [0.104445, 0.10302485],
|
'open_rate': [0.104445, 0.10302485],
|
||||||
'close_rate': [0.104969, 0.103541],
|
'close_rate': [0.104969, 0.103541],
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI]
|
'exit_reason': [SellType.ROI, SellType.ROI]
|
||||||
})
|
})
|
||||||
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
||||||
'profit_ratio': [0.03, 0.01, 0.1],
|
'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],
|
'stake_amount': [0.01, 0.01, 0.01],
|
||||||
'open_rate': [0.104445, 0.10302485, 0.122541],
|
'open_rate': [0.104445, 0.10302485, 0.122541],
|
||||||
'close_rate': [0.104969, 0.103541, 0.123541],
|
'close_rate': [0.104969, 0.103541, 0.123541],
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||||
})
|
})
|
||||||
backtestmock = MagicMock(side_effect=[
|
backtestmock = MagicMock(side_effect=[
|
||||||
{
|
{
|
||||||
|
@ -24,25 +24,25 @@ from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re
|
|||||||
|
|
||||||
def generate_result_metrics():
|
def generate_result_metrics():
|
||||||
return {
|
return {
|
||||||
'trade_count': 1,
|
'trade_count': 1,
|
||||||
'total_trades': 1,
|
'total_trades': 1,
|
||||||
'avg_profit': 0.1,
|
'avg_profit': 0.1,
|
||||||
'total_profit': 0.001,
|
'total_profit': 0.001,
|
||||||
'profit': 0.01,
|
'profit': 0.01,
|
||||||
'duration': 20.0,
|
'duration': 20.0,
|
||||||
'wins': 1,
|
'wins': 1,
|
||||||
'draws': 0,
|
'draws': 0,
|
||||||
'losses': 0,
|
'losses': 0,
|
||||||
'profit_mean': 0.01,
|
'profit_mean': 0.01,
|
||||||
'profit_total_abs': 0.001,
|
'profit_total_abs': 0.001,
|
||||||
'profit_total': 0.01,
|
'profit_total': 0.01,
|
||||||
'holding_avg': timedelta(minutes=20),
|
'holding_avg': timedelta(minutes=20),
|
||||||
'max_drawdown': 0.001,
|
'max_drawdown': 0.001,
|
||||||
'max_drawdown_abs': 0.001,
|
'max_drawdown_abs': 0.001,
|
||||||
'loss': 0.001,
|
'loss': 0.001,
|
||||||
'is_initial_point': 0.001,
|
'is_initial_point': 0.001,
|
||||||
'is_best': 1,
|
'is_best': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||||
@ -359,7 +359,7 @@ def test_hyperopt_format_results(hyperopt):
|
|||||||
"is_open": [False, False, False, True],
|
"is_open": [False, False, False, True],
|
||||||
"is_short": [False, False, False, False],
|
"is_short": [False, False, False, False],
|
||||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
"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]
|
SellType.ROI, SellType.FORCE_SELL]
|
||||||
}),
|
}),
|
||||||
'config': hyperopt.config,
|
'config': hyperopt.config,
|
||||||
@ -428,7 +428,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
|||||||
"is_open": [False, False, False, True],
|
"is_open": [False, False, False, True],
|
||||||
"is_short": [False, False, False, False],
|
"is_short": [False, False, False, False],
|
||||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
"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]
|
SellType.ROI, SellType.FORCE_SELL]
|
||||||
}),
|
}),
|
||||||
'config': hyperopt_conf,
|
'config': hyperopt_conf,
|
||||||
|
@ -18,11 +18,11 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene
|
|||||||
generate_daily_stats, generate_edge_table,
|
generate_daily_stats, generate_edge_table,
|
||||||
generate_pair_metrics,
|
generate_pair_metrics,
|
||||||
generate_periodic_breakdown_stats,
|
generate_periodic_breakdown_stats,
|
||||||
generate_sell_reason_stats,
|
generate_exit_reason_stats,
|
||||||
generate_strategy_comparison,
|
generate_strategy_comparison,
|
||||||
generate_trading_stats, show_sorted_pairlist,
|
generate_trading_stats, show_sorted_pairlist,
|
||||||
store_backtest_stats, text_table_bt_results,
|
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 freqtrade.resolvers.strategy_resolver import StrategyResolver
|
||||||
from tests.conftest import CURRENT_TEST_STRATEGY
|
from tests.conftest import CURRENT_TEST_STRATEGY
|
||||||
from tests.data.test_history import _backup_file, _clean_test_file
|
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_open": [False, False, False, True],
|
||||||
"is_short": [False, False, False, False],
|
"is_short": [False, False, False, False],
|
||||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
"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]
|
SellType.ROI, SellType.FORCE_SELL]
|
||||||
}),
|
}),
|
||||||
'config': default_conf,
|
'config': default_conf,
|
||||||
@ -87,8 +87,8 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
|||||||
'rejected_signals': 20,
|
'rejected_signals': 20,
|
||||||
'backtest_start_time': Arrow.utcnow().int_timestamp,
|
'backtest_start_time': Arrow.utcnow().int_timestamp,
|
||||||
'backtest_end_time': Arrow.utcnow().int_timestamp,
|
'backtest_end_time': Arrow.utcnow().int_timestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timerange = TimeRange.parse_timerange('1510688220-1510700340')
|
timerange = TimeRange.parse_timerange('1510688220-1510700340')
|
||||||
min_date = Arrow.fromtimestamp(1510688220)
|
min_date = Arrow.fromtimestamp(1510688220)
|
||||||
max_date = Arrow.fromtimestamp(1510700340)
|
max_date = Arrow.fromtimestamp(1510700340)
|
||||||
@ -127,7 +127,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
|||||||
"is_open": [False, False, False, True],
|
"is_open": [False, False, False, True],
|
||||||
"is_short": [False, False, False, False],
|
"is_short": [False, False, False, False],
|
||||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
"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]
|
SellType.STOP_LOSS, SellType.FORCE_SELL]
|
||||||
}),
|
}),
|
||||||
'config': default_conf,
|
'config': default_conf,
|
||||||
@ -136,7 +136,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
|||||||
'rejected_signals': 20,
|
'rejected_signals': 20,
|
||||||
'backtest_start_time': Arrow.utcnow().int_timestamp,
|
'backtest_start_time': Arrow.utcnow().int_timestamp,
|
||||||
'backtest_end_time': Arrow.utcnow().int_timestamp,
|
'backtest_end_time': Arrow.utcnow().int_timestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats = generate_backtest_stats(btdata, results, min_date, max_date)
|
stats = generate_backtest_stats(btdata, results, min_date, max_date)
|
||||||
@ -260,7 +260,7 @@ def test_generate_trading_stats(testdatadir):
|
|||||||
assert res['losses'] == 0
|
assert res['losses'] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_text_table_sell_reason():
|
def test_text_table_exit_reason():
|
||||||
|
|
||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
@ -271,7 +271,7 @@ def test_text_table_sell_reason():
|
|||||||
'wins': [2, 0, 0],
|
'wins': [2, 0, 0],
|
||||||
'draws': [0, 0, 0],
|
'draws': [0, 0, 0],
|
||||||
'losses': [0, 0, 1],
|
'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 |'
|
' -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)
|
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
|
stake_currency='BTC') == result_str
|
||||||
|
|
||||||
|
|
||||||
def test_generate_sell_reason_stats():
|
def test_generate_exit_reason_stats():
|
||||||
|
|
||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
@ -303,23 +303,23 @@ def test_generate_sell_reason_stats():
|
|||||||
'wins': [2, 0, 0],
|
'wins': [2, 0, 0],
|
||||||
'draws': [0, 0, 0],
|
'draws': [0, 0, 0],
|
||||||
'losses': [0, 0, 1],
|
'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)
|
results=results)
|
||||||
roi_result = sell_reason_stats[0]
|
roi_result = exit_reason_stats[0]
|
||||||
assert roi_result['sell_reason'] == 'roi'
|
assert roi_result['exit_reason'] == 'roi'
|
||||||
assert roi_result['trades'] == 2
|
assert roi_result['trades'] == 2
|
||||||
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
||||||
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
|
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
|
||||||
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
||||||
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
|
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 stop_result['trades'] == 1
|
||||||
assert pytest.approx(stop_result['profit_mean']) == -0.1
|
assert pytest.approx(stop_result['profit_mean']) == -0.1
|
||||||
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
|
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],
|
'wins': [2, 0, 0],
|
||||||
'draws': [0, 0, 0],
|
'draws': [0, 0, 0],
|
||||||
'losses': [0, 0, 1],
|
'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}
|
), 'config': default_conf}
|
||||||
results['TestStrategy2'] = {'results': pd.DataFrame(
|
results['TestStrategy2'] = {'results': pd.DataFrame(
|
||||||
@ -356,7 +356,7 @@ def test_text_table_strategy(default_conf):
|
|||||||
'wins': [4, 1, 0],
|
'wins': [4, 1, 0],
|
||||||
'draws': [0, 0, 0],
|
'draws': [0, 0, 0],
|
||||||
'losses': [0, 0, 1],
|
'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}
|
), 'config': default_conf}
|
||||||
|
|
||||||
|
@ -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,
|
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,
|
min_ago_open: int = None, min_ago_close: int = None,
|
||||||
profit_rate: float = 0.9
|
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()
|
trade.recalc_open_trade_value()
|
||||||
if not is_open:
|
if not is_open:
|
||||||
trade.close(open_rate * profit_rate)
|
trade.close(open_rate * profit_rate)
|
||||||
trade.sell_reason = sell_reason
|
trade.exit_reason = exit_reason
|
||||||
|
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
|
|||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=200, min_ago_close=30,
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -100,12 +100,12 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
|
|||||||
caplog.clear()
|
caplog.clear()
|
||||||
# This trade does not count, as it's closed too long ago
|
# This trade does not count, as it's closed too long ago
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=250, min_ago_close=100,
|
||||||
))
|
))
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=240, min_ago_close=30,
|
||||||
))
|
))
|
||||||
# 3 Trades closed - but the 2nd has been closed too long ago.
|
# 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()
|
caplog.clear()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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()
|
caplog.clear()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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()
|
caplog.clear()
|
||||||
# This trade does not count, as it's closed too long ago
|
# This trade does not count, as it's closed too long ago
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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 does not count for per pair stop as it's the wrong pair.
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=240, min_ago_close=30, profit_rate=0.9,
|
||||||
))
|
))
|
||||||
# 3 Trades closed - but the 2nd has been closed too long ago.
|
# 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
|
# 2nd Trade that counts with correct pair
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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()
|
caplog.clear()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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()
|
assert not PairLocks.is_global_lock()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=205, min_ago_close=35,
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -242,7 +242,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
|||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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()
|
assert not PairLocks.is_global_lock()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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
|
# Add positive trade
|
||||||
Trade.query.session.add(generate_mock_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,
|
min_ago_open=20, min_ago_close=10, profit_rate=1.15,
|
||||||
))
|
))
|
||||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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()
|
caplog.clear()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||||
))
|
))
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||||
))
|
))
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||||
))
|
))
|
||||||
# No losing trade yet ... so max_drawdown will raise exception
|
# 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')
|
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
min_ago_open=500, min_ago_close=400, profit_rate=0.9,
|
||||||
))
|
))
|
||||||
# Not locked with one trade
|
# Not locked with one trade
|
||||||
@ -326,7 +326,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
|||||||
assert not PairLocks.is_global_lock()
|
assert not PairLocks.is_global_lock()
|
||||||
|
|
||||||
Trade.query.session.add(generate_mock_trade(
|
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,
|
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!)
|
# Winning trade ... (should not lock, does not change drawdown!)
|
||||||
Trade.query.session.add(generate_mock_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=320, min_ago_close=410, profit_rate=1.5,
|
min_ago_open=320, min_ago_close=410, profit_rate=1.5,
|
||||||
))
|
))
|
||||||
assert not freqtrade.protections.global_stop()
|
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%
|
# Add additional negative trade, causing a loss of > 15%
|
||||||
Trade.query.session.add(generate_mock_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=0.8,
|
min_ago_open=20, min_ago_close=10, profit_rate=0.8,
|
||||||
))
|
))
|
||||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||||
|
@ -64,7 +64,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'open_rate_requested': ANY,
|
'open_rate_requested': ANY,
|
||||||
'open_trade_value': 0.0010025,
|
'open_trade_value': 0.0010025,
|
||||||
'close_rate_requested': ANY,
|
'close_rate_requested': ANY,
|
||||||
'sell_reason': ANY,
|
'exit_reason': ANY,
|
||||||
'exit_order_status': ANY,
|
'exit_order_status': ANY,
|
||||||
'min_rate': ANY,
|
'min_rate': ANY,
|
||||||
'max_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_rate_requested': ANY,
|
||||||
'open_trade_value': ANY,
|
'open_trade_value': ANY,
|
||||||
'close_rate_requested': ANY,
|
'close_rate_requested': ANY,
|
||||||
'sell_reason': ANY,
|
'exit_reason': ANY,
|
||||||
'exit_order_status': ANY,
|
'exit_order_status': ANY,
|
||||||
'min_rate': ANY,
|
'min_rate': ANY,
|
||||||
'max_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)
|
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:
|
limit_sell_order, mocker) -> None:
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
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.close_date = datetime.utcnow()
|
||||||
trade.is_open = False
|
trade.is_open = False
|
||||||
res = rpc._rpc_sell_reason_performance(None)
|
res = rpc._rpc_exit_reason_performance(None)
|
||||||
|
|
||||||
assert len(res) == 1
|
assert len(res) == 1
|
||||||
assert res[0]['sell_reason'] == 'Other'
|
assert res[0]['exit_reason'] == 'Other'
|
||||||
assert res[0]['count'] == 1
|
assert res[0]['count'] == 1
|
||||||
assert prec_satoshi(res[0]['profit_pct'], 6.2)
|
assert prec_satoshi(res[0]['profit_pct'], 6.2)
|
||||||
|
|
||||||
trade.sell_reason = "TEST1"
|
trade.exit_reason = "TEST1"
|
||||||
res = rpc._rpc_sell_reason_performance(None)
|
res = rpc._rpc_exit_reason_performance(None)
|
||||||
|
|
||||||
assert len(res) == 1
|
assert len(res) == 1
|
||||||
assert res[0]['sell_reason'] == 'TEST1'
|
assert res[0]['exit_reason'] == 'TEST1'
|
||||||
assert res[0]['count'] == 1
|
assert res[0]['count'] == 1
|
||||||
assert prec_satoshi(res[0]['profit_pct'], 6.2)
|
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('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -970,21 +970,21 @@ def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee):
|
|||||||
create_mock_trades(fee)
|
create_mock_trades(fee)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
|
|
||||||
res = rpc._rpc_sell_reason_performance(None)
|
res = rpc._rpc_exit_reason_performance(None)
|
||||||
|
|
||||||
assert len(res) == 2
|
assert len(res) == 2
|
||||||
assert res[0]['sell_reason'] == 'sell_signal'
|
assert res[0]['exit_reason'] == 'sell_signal'
|
||||||
assert res[0]['count'] == 1
|
assert res[0]['count'] == 1
|
||||||
assert prec_satoshi(res[0]['profit_pct'], 0.5)
|
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 res[1]['count'] == 1
|
||||||
assert prec_satoshi(res[1]['profit_pct'], 1.0)
|
assert prec_satoshi(res[1]['profit_pct'], 1.0)
|
||||||
|
|
||||||
# Test for a specific pair
|
# 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 len(res) == 1
|
||||||
assert res[0]['count'] == 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)
|
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)
|
assert prec_satoshi(res[0]['profit_pct'], 6.2)
|
||||||
|
|
||||||
trade.enter_tag = "TESTBUY"
|
trade.enter_tag = "TESTBUY"
|
||||||
trade.sell_reason = "TESTSELL"
|
trade.exit_reason = "TESTSELL"
|
||||||
res = rpc._rpc_mix_tag_performance(None)
|
res = rpc._rpc_mix_tag_performance(None)
|
||||||
|
|
||||||
assert len(res) == 1
|
assert len(res) == 1
|
||||||
|
@ -816,14 +816,14 @@ def test_api_stats(botclient, mocker, ticker, fee, markets, is_short):
|
|||||||
rc = client_get(client, f"{BASE_URI}/stats")
|
rc = client_get(client, f"{BASE_URI}/stats")
|
||||||
assert_response(rc, 200)
|
assert_response(rc, 200)
|
||||||
assert 'durations' in rc.json()
|
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)
|
create_mock_trades(fee, is_short=is_short)
|
||||||
|
|
||||||
rc = client_get(client, f"{BASE_URI}/stats")
|
rc = client_get(client, f"{BASE_URI}/stats")
|
||||||
assert_response(rc, 200)
|
assert_response(rc, 200)
|
||||||
assert 'durations' in rc.json()
|
assert 'durations' in rc.json()
|
||||||
assert 'sell_reasons' in rc.json()
|
assert 'exit_reasons' in rc.json()
|
||||||
|
|
||||||
assert 'wins' in rc.json()['durations']
|
assert 'wins' in rc.json()['durations']
|
||||||
assert 'losses' 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_order_id': open_order_id,
|
||||||
'open_rate_requested': ANY,
|
'open_rate_requested': ANY,
|
||||||
'open_trade_value': open_trade_value,
|
'open_trade_value': open_trade_value,
|
||||||
'sell_reason': None,
|
'exit_reason': None,
|
||||||
'exit_order_status': None,
|
'exit_order_status': None,
|
||||||
'strategy': CURRENT_TEST_STRATEGY,
|
'strategy': CURRENT_TEST_STRATEGY,
|
||||||
'buy_tag': None,
|
'buy_tag': None,
|
||||||
@ -1146,7 +1146,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
|||||||
'open_order_id': '123456',
|
'open_order_id': '123456',
|
||||||
'open_rate_requested': None,
|
'open_rate_requested': None,
|
||||||
'open_trade_value': 0.24605460,
|
'open_trade_value': 0.24605460,
|
||||||
'sell_reason': None,
|
'exit_reason': None,
|
||||||
'exit_order_status': None,
|
'exit_order_status': None,
|
||||||
'strategy': CURRENT_TEST_STRATEGY,
|
'strategy': CURRENT_TEST_STRATEGY,
|
||||||
'buy_tag': None,
|
'buy_tag': None,
|
||||||
|
@ -962,7 +962,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
|||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'buy_tag': ANY,
|
'buy_tag': ANY,
|
||||||
'enter_tag': ANY,
|
'enter_tag': ANY,
|
||||||
'sell_reason': SellType.FORCE_SELL.value,
|
'exit_reason': SellType.FORCE_SELL.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': ANY,
|
'close_rate': ANY,
|
||||||
@ -1030,7 +1030,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
|||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'buy_tag': ANY,
|
'buy_tag': ANY,
|
||||||
'enter_tag': ANY,
|
'enter_tag': ANY,
|
||||||
'sell_reason': SellType.FORCE_SELL.value,
|
'exit_reason': SellType.FORCE_SELL.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': ANY,
|
'close_rate': ANY,
|
||||||
@ -1088,7 +1088,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
|||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'buy_tag': ANY,
|
'buy_tag': ANY,
|
||||||
'enter_tag': ANY,
|
'enter_tag': ANY,
|
||||||
'sell_reason': SellType.FORCE_SELL.value,
|
'exit_reason': SellType.FORCE_SELL.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': 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]
|
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:
|
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'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
|
# Simulate fulfilled LIMIT_BUY order for trade
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
|
|
||||||
trade.sell_reason = 'TESTSELL'
|
trade.exit_reason = 'TESTSELL'
|
||||||
# Simulate fulfilled LIMIT_SELL order for trade
|
# Simulate fulfilled LIMIT_SELL order for trade
|
||||||
trade.update(limit_sell_order)
|
trade.update(limit_sell_order)
|
||||||
|
|
||||||
trade.close_date = datetime.utcnow()
|
trade.close_date = datetime.utcnow()
|
||||||
trade.is_open = False
|
trade.is_open = False
|
||||||
context = MagicMock()
|
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 msg_mock.call_count == 1
|
||||||
assert 'Sell Reason Performance' in msg_mock.call_args_list[0][0][0]
|
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]
|
assert '<code>TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||||
context.args = [trade.pair]
|
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
|
assert msg_mock.call_count == 2
|
||||||
|
|
||||||
msg_mock.reset_mock()
|
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'))
|
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 msg_mock.call_count == 1
|
||||||
assert "Error" in msg_mock.call_args_list[0][0][0]
|
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.update(limit_buy_order)
|
||||||
|
|
||||||
trade.enter_tag = "TESTBUY"
|
trade.enter_tag = "TESTBUY"
|
||||||
trade.sell_reason = "TESTSELL"
|
trade.exit_reason = "TESTSELL"
|
||||||
|
|
||||||
# Simulate fulfilled LIMIT_SELL order for trade
|
# Simulate fulfilled LIMIT_SELL order for trade
|
||||||
trade.update(limit_sell_order)
|
trade.update(limit_sell_order)
|
||||||
@ -1826,7 +1826,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
|||||||
'stake_currency': 'ETH',
|
'stake_currency': 'ETH',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'enter_tag': 'buy_signal1',
|
'enter_tag': 'buy_signal1',
|
||||||
'sell_reason': SellType.STOP_LOSS.value,
|
'exit_reason': SellType.STOP_LOSS.value,
|
||||||
'open_date': arrow.utcnow().shift(hours=-1),
|
'open_date': arrow.utcnow().shift(hours=-1),
|
||||||
'close_date': arrow.utcnow(),
|
'close_date': arrow.utcnow(),
|
||||||
})
|
})
|
||||||
@ -1860,7 +1860,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
|||||||
'profit_ratio': -0.57405275,
|
'profit_ratio': -0.57405275,
|
||||||
'stake_currency': 'ETH',
|
'stake_currency': 'ETH',
|
||||||
'enter_tag': 'buy_signal1',
|
'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),
|
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
||||||
'close_date': arrow.utcnow(),
|
'close_date': arrow.utcnow(),
|
||||||
})
|
})
|
||||||
@ -1939,7 +1939,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
|
|||||||
'profit_ratio': -0.57405275,
|
'profit_ratio': -0.57405275,
|
||||||
'stake_currency': 'ETH',
|
'stake_currency': 'ETH',
|
||||||
'enter_tag': enter_signal,
|
'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),
|
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
||||||
'close_date': arrow.utcnow(),
|
'close_date': arrow.utcnow(),
|
||||||
})
|
})
|
||||||
@ -2063,7 +2063,7 @@ def test_send_msg_sell_notification_no_fiat(
|
|||||||
'stake_currency': 'ETH',
|
'stake_currency': 'ETH',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'enter_tag': enter_signal,
|
'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),
|
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
|
||||||
'close_date': arrow.utcnow(),
|
'close_date': arrow.utcnow(),
|
||||||
})
|
})
|
||||||
@ -2085,13 +2085,13 @@ def test_send_msg_sell_notification_no_fiat(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('msg,expected', [
|
@pytest.mark.parametrize('msg,expected', [
|
||||||
({'profit_percent': 20.1, 'sell_reason': 'roi'}, "\N{ROCKET}"),
|
({'profit_percent': 20.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
|
||||||
({'profit_percent': 5.1, 'sell_reason': 'roi'}, "\N{ROCKET}"),
|
({'profit_percent': 5.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
|
||||||
({'profit_percent': 2.56, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
({'profit_percent': 2.56, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||||
({'profit_percent': 1.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
({'profit_percent': 1.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||||
({'profit_percent': 0.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
({'profit_percent': 0.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||||
({'profit_percent': -5.0, 'sell_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
|
({'profit_percent': -5.0, 'exit_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
|
||||||
({'profit_percent': -2.0, 'sell_reason': 'sell_signal'}, "\N{CROSS MARK}"),
|
({'profit_percent': -2.0, 'exit_reason': 'sell_signal'}, "\N{CROSS MARK}"),
|
||||||
])
|
])
|
||||||
def test__sell_emoji(default_conf, mocker, msg, expected):
|
def test__sell_emoji(default_conf, mocker, msg, expected):
|
||||||
del default_conf['fiat_display_currency']
|
del default_conf['fiat_display_currency']
|
||||||
|
@ -244,7 +244,7 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
'profit_amount': 0.001,
|
'profit_amount': 0.001,
|
||||||
'profit_ratio': 0.20,
|
'profit_ratio': 0.20,
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
'sell_reason': SellType.STOP_LOSS.value
|
'exit_reason': SellType.STOP_LOSS.value
|
||||||
}
|
}
|
||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
@ -269,7 +269,7 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
'profit_amount': 0.001,
|
'profit_amount': 0.001,
|
||||||
'profit_ratio': 0.20,
|
'profit_ratio': 0.20,
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
'sell_reason': SellType.STOP_LOSS.value
|
'exit_reason': SellType.STOP_LOSS.value
|
||||||
}
|
}
|
||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
@ -294,7 +294,7 @@ def test_send_msg_webhook(default_conf, mocker):
|
|||||||
'profit_amount': 0.001,
|
'profit_amount': 0.001,
|
||||||
'profit_ratio': 0.20,
|
'profit_ratio': 0.20,
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
'sell_reason': SellType.STOP_LOSS.value
|
'exit_reason': SellType.STOP_LOSS.value
|
||||||
}
|
}
|
||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
|
@ -46,7 +46,7 @@ def test_strategy_test_v3(result, fee, is_short, side):
|
|||||||
current_time=datetime.utcnow(),
|
current_time=datetime.utcnow(),
|
||||||
side=side) is True
|
side=side) is True
|
||||||
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
|
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(),
|
current_time=datetime.utcnow(),
|
||||||
side=side) is True
|
side=side) is True
|
||||||
|
|
||||||
|
@ -488,7 +488,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
|
|||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
assert res.sell_flag is True
|
assert res.sell_flag is True
|
||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
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')
|
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)
|
low=None, high=None)
|
||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
assert res.sell_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_flag is True
|
assert res.sell_flag is True
|
||||||
assert res.sell_reason == 'hello world'
|
assert res.exit_reason == 'hello world'
|
||||||
|
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
strategy.custom_sell = MagicMock(return_value='h' * 100)
|
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)
|
low=None, high=None)
|
||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
assert res.sell_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_flag is True
|
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)
|
assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)
|
||||||
|
|
||||||
|
|
||||||
@ -522,7 +522,7 @@ def test_leverage_callback(default_conf, side) -> None:
|
|||||||
proposed_leverage=1.0,
|
proposed_leverage=1.0,
|
||||||
max_leverage=5.0,
|
max_leverage=5.0,
|
||||||
side=side,
|
side=side,
|
||||||
) == 1
|
) == 1
|
||||||
|
|
||||||
default_conf['strategy'] = CURRENT_TEST_STRATEGY
|
default_conf['strategy'] = CURRENT_TEST_STRATEGY
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -533,7 +533,7 @@ def test_leverage_callback(default_conf, side) -> None:
|
|||||||
proposed_leverage=1.0,
|
proposed_leverage=1.0,
|
||||||
max_leverage=5.0,
|
max_leverage=5.0,
|
||||||
side=side,
|
side=side,
|
||||||
) == 3
|
) == 3
|
||||||
|
|
||||||
|
|
||||||
def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
|
def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
|
||||||
|
@ -234,7 +234,7 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
|
|||||||
assert freqtrade.handle_trade(trade) is not ignore_strat_sl
|
assert freqtrade.handle_trade(trade) is not ignore_strat_sl
|
||||||
if not ignore_strat_sl:
|
if not ignore_strat_sl:
|
||||||
assert log_has_re('Exit for NEO/BTC detected. Reason: stop_loss.*', caplog)
|
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:
|
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()
|
caplog.clear()
|
||||||
freqtrade.create_stoploss_order(trade, 200)
|
freqtrade.create_stoploss_order(trade, 200)
|
||||||
assert trade.stoploss_order_id is None
|
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("Unable to place a stoploss order on exchange. ", caplog)
|
||||||
assert log_has("Exiting the trade forcefully", 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
|
# Rpc is sending first buy, then sell
|
||||||
assert rpc_mock.call_count == 2
|
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'
|
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.close_profit == close_profit
|
||||||
assert trade.calc_profit() == 5.685
|
assert trade.calc_profit() == 5.685
|
||||||
assert trade.close_date is not None
|
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])
|
@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(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
|
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 rpc_mock.call_count == 0
|
||||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
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(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
|
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
|
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,
|
'profit_ratio': 0.00493809 if is_short else 0.09451372,
|
||||||
'stake_currency': 'USDT',
|
'stake_currency': 'USDT',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'sell_reason': SellType.ROI.value,
|
'exit_reason': SellType.ROI.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': 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(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'],
|
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
|
assert rpc_mock.call_count == 2
|
||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
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,
|
'profit_ratio': -0.0945681 if is_short else -1.247e-05,
|
||||||
'stake_currency': 'USDT',
|
'stake_currency': 'USDT',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'sell_reason': SellType.STOP_LOSS.value,
|
'exit_reason': SellType.STOP_LOSS.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': ANY,
|
'close_rate': ANY,
|
||||||
@ -3003,7 +3003,7 @@ def test_execute_trade_exit_custom_exit_price(
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
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
|
# 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,
|
'profit_ratio': profit_ratio,
|
||||||
'stake_currency': 'USDT',
|
'stake_currency': 'USDT',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'sell_reason': SellType.SELL_SIGNAL.value,
|
'exit_reason': SellType.SELL_SIGNAL.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': 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
|
trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
|
||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'],
|
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
|
assert rpc_mock.call_count == 2
|
||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
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,
|
'profit_ratio': -0.00501253 if is_short else -0.01493766,
|
||||||
'stake_currency': 'USDT',
|
'stake_currency': 'USDT',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'sell_reason': SellType.STOP_LOSS.value,
|
'exit_reason': SellType.STOP_LOSS.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': ANY,
|
'close_rate': ANY,
|
||||||
@ -3134,7 +3134,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
|
|||||||
trade.stoploss_order_id = "abcd"
|
trade.stoploss_order_id = "abcd"
|
||||||
|
|
||||||
freqtrade.execute_trade_exit(trade=trade, limit=1234,
|
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 create_order_mock.call_count == 2
|
||||||
assert log_has('Could not cancel stoploss order abcd', caplog)
|
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(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
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()
|
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)
|
freqtrade.exit_positions(trades)
|
||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
assert trade.is_open is False
|
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_count == 3
|
||||||
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
|
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
|
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(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
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
|
assert not trade.is_open
|
||||||
@ -3355,7 +3355,7 @@ def test_execute_trade_exit_market_order(
|
|||||||
'profit_ratio': profit_ratio,
|
'profit_ratio': profit_ratio,
|
||||||
'stake_currency': 'USDT',
|
'stake_currency': 'USDT',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'sell_reason': SellType.ROI.value,
|
'exit_reason': SellType.ROI.value,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'close_date': ANY,
|
'close_date': ANY,
|
||||||
'close_rate': 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
|
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(
|
assert not freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
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
|
assert mock_insuf.call_count == 1
|
||||||
|
|
||||||
@ -3561,7 +3561,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'],
|
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'])
|
trade.close(ticker_usdt_sell_down()['bid'])
|
||||||
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
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:
|
else:
|
||||||
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
|
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
|
||||||
assert freqtrade.handle_trade(trade) is True
|
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", [
|
@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"stoploss is {(2.0 * val1 * stop_multi):6f}, "
|
||||||
f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
|
f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
|
||||||
caplog)
|
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', [
|
@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"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",
|
f"trade opened at {2.2 if is_short else 2.0}00000",
|
||||||
caplog)
|
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])
|
@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
|
# Test if buy-signal is absent
|
||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
assert freqtrade.handle_trade(trade) is True
|
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,
|
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,
|
trade=trade,
|
||||||
# The values of the next 2 params are irrelevant for this test
|
# The values of the next 2 params are irrelevant for this test
|
||||||
limit=ticker_usdt_sell_up()['bid'],
|
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(
|
assert trade.funding_fees == pytest.approx(sum(
|
||||||
trade.amount *
|
trade.amount *
|
||||||
|
@ -111,15 +111,15 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
assert wallets_mock.call_count == 4
|
assert wallets_mock.call_count == 4
|
||||||
|
|
||||||
trade = trades[0]
|
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
|
assert not trade.is_open
|
||||||
|
|
||||||
trade = trades[1]
|
trade = trades[1]
|
||||||
assert not trade.sell_reason
|
assert not trade.exit_reason
|
||||||
assert trade.is_open
|
assert trade.is_open
|
||||||
|
|
||||||
trade = trades[2]
|
trade = trades[2]
|
||||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
assert trade.exit_reason == SellType.SELL_SIGNAL.value
|
||||||
assert not trade.is_open
|
assert not trade.is_open
|
||||||
|
|
||||||
|
|
||||||
|
@ -1175,7 +1175,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
|||||||
stop_loss FLOAT,
|
stop_loss FLOAT,
|
||||||
initial_stop_loss FLOAT,
|
initial_stop_loss FLOAT,
|
||||||
max_rate FLOAT,
|
max_rate FLOAT,
|
||||||
sell_reason VARCHAR,
|
exit_reason VARCHAR,
|
||||||
strategy VARCHAR,
|
strategy VARCHAR,
|
||||||
ticker_interval INTEGER,
|
ticker_interval INTEGER,
|
||||||
stoploss_order_id VARCHAR,
|
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.min_rate is None
|
||||||
assert trade.stop_loss == 0.0
|
assert trade.stop_loss == 0.0
|
||||||
assert trade.initial_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.strategy is None
|
||||||
assert trade.timeframe == '5m'
|
assert trade.timeframe == '5m'
|
||||||
assert trade.stoploss_order_id == 'stop_order_id222'
|
assert trade.stoploss_order_id == 'stop_order_id222'
|
||||||
@ -1587,7 +1587,7 @@ def test_to_json(default_conf, fee):
|
|||||||
'profit_ratio': None,
|
'profit_ratio': None,
|
||||||
'profit_pct': None,
|
'profit_pct': None,
|
||||||
'profit_abs': None,
|
'profit_abs': None,
|
||||||
'sell_reason': None,
|
'exit_reason': None,
|
||||||
'exit_order_status': None,
|
'exit_order_status': None,
|
||||||
'stop_loss_abs': None,
|
'stop_loss_abs': None,
|
||||||
'stop_loss_ratio': None,
|
'stop_loss_ratio': None,
|
||||||
@ -1672,7 +1672,7 @@ def test_to_json(default_conf, fee):
|
|||||||
'open_order_id': None,
|
'open_order_id': None,
|
||||||
'open_rate_requested': None,
|
'open_rate_requested': None,
|
||||||
'open_trade_value': 12.33075,
|
'open_trade_value': 12.33075,
|
||||||
'sell_reason': None,
|
'exit_reason': None,
|
||||||
'exit_order_status': None,
|
'exit_order_status': None,
|
||||||
'strategy': None,
|
'strategy': None,
|
||||||
'buy_tag': 'buys_signal_001',
|
'buy_tag': 'buys_signal_001',
|
||||||
@ -2126,7 +2126,7 @@ def test_Trade_object_idem():
|
|||||||
'get_open_trades_without_assigned_fees',
|
'get_open_trades_without_assigned_fees',
|
||||||
'get_open_order_trades',
|
'get_open_order_trades',
|
||||||
'get_trades',
|
'get_trades',
|
||||||
'get_sell_reason_performance',
|
'get_exit_reason_performance',
|
||||||
'get_enter_tag_performance',
|
'get_enter_tag_performance',
|
||||||
'get_mix_tag_performance',
|
'get_mix_tag_performance',
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
2
tests/testdata/backtest-result_new.json
vendored
2
tests/testdata/backtest-result_new.json
vendored
File diff suppressed because one or more lines are too long
10
tests/testdata/strategy_SampleStrategy.fthypt
vendored
10
tests/testdata/strategy_SampleStrategy.fthypt
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user