Merge pull request #5299 from kevinjulian/feat/kevinjulian/add-buy-signal-name

Add buy signal name
This commit is contained in:
Matthias 2021-07-30 08:23:11 +02:00 committed by GitHub
commit 138b126d03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 261 additions and 115 deletions

View File

@ -114,6 +114,36 @@ class AwesomeStrategy(IStrategy):
See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks. See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks.
## Buy Tag
When your strategy has multiple buy signals, you can name the signal that triggered.
Then you can access you buy signal on `custom_sell`
```python
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe['rsi'] < 35) &
(dataframe['volume'] > 0)
),
['buy', 'buy_tag']] = (1, 'buy_signal_rsi')
return dataframe
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
if trade.buy_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80:
return 'sell_signal_rsi'
return None
```
!!! Note
`buy_tag` is limited to 100 characters, remaining data will be truncated.
## Custom stoploss ## Custom stoploss
The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss. The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss.

View File

@ -83,6 +83,7 @@ Possible parameters are:
* `fiat_currency` * `fiat_currency`
* `order_type` * `order_type`
* `current_rate` * `current_rate`
* `buy_tag`
### Webhookbuycancel ### Webhookbuycancel
@ -100,6 +101,7 @@ Possible parameters are:
* `fiat_currency` * `fiat_currency`
* `order_type` * `order_type`
* `current_rate` * `current_rate`
* `buy_tag`
### Webhookbuyfill ### Webhookbuyfill
@ -115,6 +117,7 @@ Possible parameters are:
* `stake_amount` * `stake_amount`
* `stake_currency` * `stake_currency`
* `fiat_currency` * `fiat_currency`
* `buy_tag`
### Webhooksell ### Webhooksell

View File

@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'fee_open', 'fee_close', 'trade_duration', 'fee_open', 'fee_close', 'trade_duration',
'profit_ratio', 'profit_abs', 'sell_reason', 'profit_ratio', 'profit_abs', 'sell_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', ] 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag']
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:

View File

@ -3,5 +3,5 @@ from freqtrade.enums.backteststate import BacktestState
from freqtrade.enums.rpcmessagetype import RPCMessageType from freqtrade.enums.rpcmessagetype import RPCMessageType
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
from freqtrade.enums.selltype import SellType from freqtrade.enums.selltype import SellType
from freqtrade.enums.signaltype import SignalType from freqtrade.enums.signaltype import SignalTagType, SignalType
from freqtrade.enums.state import State from freqtrade.enums.state import State

View File

@ -7,3 +7,10 @@ class SignalType(Enum):
""" """
BUY = "buy" BUY = "buy"
SELL = "sell" SELL = "sell"
class SignalTagType(Enum):
"""
Enum for signal columns
"""
BUY_TAG = "buy_tag"

View File

@ -420,20 +420,24 @@ class FreqtradeBot(LoggingMixin):
return False return False
# running get_signal on historical data fetched # running get_signal on historical data fetched
(buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) (buy, sell, buy_tag) = self.strategy.get_signal(
pair,
self.strategy.timeframe,
analyzed_df
)
if buy and not sell: if buy and not sell:
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge) stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {}) bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {})
if ((bid_check_dom.get('enabled', False)) and if ((bid_check_dom.get('enabled', False)) and
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)): (bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
if self._check_depth_of_market_buy(pair, bid_check_dom): if self._check_depth_of_market_buy(pair, bid_check_dom):
return self.execute_buy(pair, stake_amount) return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
else: else:
return False return False
return self.execute_buy(pair, stake_amount) return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
else: else:
return False return False
@ -462,7 +466,7 @@ class FreqtradeBot(LoggingMixin):
return False return False
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None, def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
forcebuy: bool = False) -> bool: forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
""" """
Executes a limit buy for the given pair Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY :param pair: pair for which we want to create a LIMIT_BUY
@ -565,6 +569,7 @@ class FreqtradeBot(LoggingMixin):
exchange=self.exchange.id, exchange=self.exchange.id,
open_order_id=order_id, open_order_id=order_id,
strategy=self.strategy.get_strategy_name(), strategy=self.strategy.get_strategy_name(),
buy_tag=buy_tag,
timeframe=timeframe_to_minutes(self.config['timeframe']) timeframe=timeframe_to_minutes(self.config['timeframe'])
) )
trade.orders.append(order_obj) trade.orders.append(order_obj)
@ -590,6 +595,7 @@ class FreqtradeBot(LoggingMixin):
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
'type': RPCMessageType.BUY, 'type': RPCMessageType.BUY,
'buy_tag': trade.buy_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'limit': trade.open_rate, 'limit': trade.open_rate,
@ -614,6 +620,7 @@ class FreqtradeBot(LoggingMixin):
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
'type': RPCMessageType.BUY_CANCEL, 'type': RPCMessageType.BUY_CANCEL,
'buy_tag': trade.buy_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'limit': trade.open_rate, 'limit': trade.open_rate,
@ -634,6 +641,7 @@ class FreqtradeBot(LoggingMixin):
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
'type': RPCMessageType.BUY_FILL, 'type': RPCMessageType.BUY_FILL,
'buy_tag': trade.buy_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'open_rate': trade.open_rate, 'open_rate': trade.open_rate,
@ -692,7 +700,11 @@ class FreqtradeBot(LoggingMixin):
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
self.strategy.timeframe) self.strategy.timeframe)
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df) (buy, sell, _) = self.strategy.get_signal(
trade.pair,
self.strategy.timeframe,
analyzed_df
)
logger.debug('checking sell') logger.debug('checking sell')
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")

View File

@ -43,6 +43,7 @@ CLOSE_IDX = 3
SELL_IDX = 4 SELL_IDX = 4
LOW_IDX = 5 LOW_IDX = 5
HIGH_IDX = 6 HIGH_IDX = 6
BUY_TAG_IDX = 7
class Backtesting: class Backtesting:
@ -217,9 +218,13 @@ class Backtesting:
for pair, pair_data in processed.items(): for pair, pair_data in processed.items():
self.check_abort() self.check_abort()
self.progress.increment() self.progress.increment()
has_buy_tag = 'buy_tag' in pair_data
headers = headers + ['buy_tag'] if has_buy_tag else headers
if not pair_data.empty: if not pair_data.empty:
pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist
pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist
if has_buy_tag:
pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist
df_analyzed = self.strategy.advise_sell( df_analyzed = self.strategy.advise_sell(
self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
@ -230,6 +235,8 @@ class Backtesting:
# from the previous candle # from the previous candle
df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1)
df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1) df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1)
if has_buy_tag:
df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1)
df_analyzed.drop(df_analyzed.head(1).index, inplace=True) df_analyzed.drop(df_analyzed.head(1).index, inplace=True)
@ -264,7 +271,7 @@ class Backtesting:
# Worst case: price reaches stop_positive_offset and dives down. # Worst case: price reaches stop_positive_offset and dives down.
stop_rate = (sell_row[OPEN_IDX] * stop_rate = (sell_row[OPEN_IDX] *
(1 + abs(self.strategy.trailing_stop_positive_offset) - (1 + abs(self.strategy.trailing_stop_positive_offset) -
abs(self.strategy.trailing_stop_positive))) abs(self.strategy.trailing_stop_positive)))
else: else:
# Worst case: price ticks tiny bit above open and dives down. # Worst case: price ticks tiny bit above open and dives down.
stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct)) stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct))
@ -360,6 +367,7 @@ class Backtesting:
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
# Enter trade # Enter trade
has_buy_tag = len(row) >= BUY_TAG_IDX + 1
trade = LocalTrade( trade = LocalTrade(
pair=pair, pair=pair,
open_rate=row[OPEN_IDX], open_rate=row[OPEN_IDX],
@ -369,6 +377,7 @@ class Backtesting:
fee_open=self.fee, fee_open=self.fee,
fee_close=self.fee, fee_close=self.fee,
is_open=True, is_open=True,
buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None,
exchange='backtesting', exchange='backtesting',
) )
return trade return trade

View File

@ -47,6 +47,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
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') sell_reason = get_column_def(cols, 'sell_reason', 'null')
strategy = get_column_def(cols, 'strategy', 'null') strategy = get_column_def(cols, 'strategy', 'null')
buy_tag = get_column_def(cols, 'buy_tag', 'null')
# If ticker-interval existed use that, else null. # If ticker-interval existed use that, else null.
if has_column(cols, 'ticker_interval'): if has_column(cols, 'ticker_interval'):
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval') timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
@ -80,7 +81,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, sell_order_status, strategy, max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag,
timeframe, open_trade_value, close_profit_abs timeframe, open_trade_value, close_profit_abs
) )
select id, lower(exchange), select id, lower(exchange),
@ -103,7 +104,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
{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, {sell_reason} sell_reason,
{sell_order_status} sell_order_status, {sell_order_status} sell_order_status,
{strategy} strategy, {timeframe} timeframe, {strategy} strategy, {buy_tag} buy_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
from {table_back_name} from {table_back_name}
""")) """))
@ -160,7 +161,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
table_back_name = get_backup_name(tabs, 'trades_bak') table_back_name = get_backup_name(tabs, 'trades_bak')
# Check for latest column # Check for latest column
if not has_column(cols, 'open_trade_value'): if not has_column(cols, 'buy_tag'):
logger.info(f'Running database migration for trades - backup: {table_back_name}') logger.info(f'Running database migration for trades - backup: {table_back_name}')
migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) migrate_trades_table(decl_base, inspector, engine, table_back_name, cols)
# Reread columns - the above recreated the table! # Reread columns - the above recreated the table!

View File

@ -257,6 +257,7 @@ class LocalTrade():
sell_reason: str = '' sell_reason: str = ''
sell_order_status: str = '' sell_order_status: str = ''
strategy: str = '' strategy: str = ''
buy_tag: Optional[str] = None
timeframe: Optional[int] = None timeframe: Optional[int] = None
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -288,6 +289,7 @@ class LocalTrade():
'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None, 'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None,
'stake_amount': round(self.stake_amount, 8), 'stake_amount': round(self.stake_amount, 8),
'strategy': self.strategy, 'strategy': self.strategy,
'buy_tag': self.buy_tag,
'timeframe': self.timeframe, 'timeframe': self.timeframe,
'fee_open': self.fee_open, 'fee_open': self.fee_open,
@ -636,7 +638,7 @@ class LocalTrade():
# skip case if trailing-stop changed the stoploss already. # skip case if trailing-stop changed the stoploss already.
if (trade.stop_loss == trade.initial_stop_loss if (trade.stop_loss == trade.initial_stop_loss
and trade.initial_stop_loss_pct != desired_stoploss): and trade.initial_stop_loss_pct != desired_stoploss):
# Stoploss value got changed # Stoploss value got changed
logger.info(f"Stoploss for {trade} needs adjustment...") logger.info(f"Stoploss for {trade} needs adjustment...")
@ -703,6 +705,7 @@ class Trade(_DECL_BASE, LocalTrade):
sell_reason = Column(String(100), nullable=True) sell_reason = Column(String(100), nullable=True)
sell_order_status = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True)
strategy = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True)
buy_tag = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True) timeframe = Column(Integer, nullable=True)
def __init__(self, **kwargs): def __init__(self, **kwargs):

View File

@ -151,6 +151,7 @@ class TradeSchema(BaseModel):
amount_requested: float amount_requested: float
stake_amount: float stake_amount: float
strategy: str strategy: str
buy_tag: Optional[str]
timeframe: int timeframe: int
fee_open: Optional[float] fee_open: Optional[float]
fee_open_cost: Optional[float] fee_open_cost: Optional[float]

View File

@ -208,15 +208,25 @@ class Telegram(RPCHandler):
else: else:
msg['stake_amount_fiat'] = 0 msg['stake_amount_fiat'] = 0
message = (f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}" content = []
f" (#{msg['trade_id']})\n" content.append(
f"*Amount:* `{msg['amount']:.8f}`\n" f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}"
f"*Open Rate:* `{msg['limit']:.8f}`\n" f" (#{msg['trade_id']})\n"
f"*Current Rate:* `{msg['current_rate']:.8f}`\n" )
f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}") if msg.get('buy_tag', None):
content.append(f"*Buy Tag:* `{msg['buy_tag']}`\n")
content.append(f"*Amount:* `{msg['amount']:.8f}`\n")
content.append(f"*Open Rate:* `{msg['limit']:.8f}`\n")
content.append(f"*Current Rate:* `{msg['current_rate']:.8f}`\n")
content.append(
f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}"
)
if msg.get('fiat_currency', None): if msg.get('fiat_currency', None):
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}" content.append(
f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
)
message = ''.join(content)
message += ")`" message += ")`"
return message return message
@ -354,6 +364,7 @@ class Telegram(RPCHandler):
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`", "*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
"*Current Pair:* {pair}", "*Current Pair:* {pair}",
"*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Amount:* `{amount} ({stake_amount} {base_currency})`",
"*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "",
"*Open Rate:* `{open_rate:.8f}`", "*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "", "*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
"*Current Rate:* `{current_rate:.8f}`", "*Current Rate:* `{current_rate:.8f}`",

View File

@ -13,7 +13,7 @@ from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes from freqtrade.constants import ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import SellType, SignalType from freqtrade.enums import SellType, SignalTagType, SignalType
from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from freqtrade.exchange.exchange import timeframe_to_next_date from freqtrade.exchange.exchange import timeframe_to_next_date
@ -422,6 +422,7 @@ class IStrategy(ABC, HyperStrategyMixin):
logger.debug("Skipping TA Analysis for already analyzed candle") logger.debug("Skipping TA Analysis for already analyzed candle")
dataframe['buy'] = 0 dataframe['buy'] = 0
dataframe['sell'] = 0 dataframe['sell'] = 0
dataframe['buy_tag'] = None
# Other Defs in strategy that want to be called every loop here # Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata) # twitter_sell = self.watch_twitter_feed(dataframe, metadata)
@ -496,7 +497,12 @@ class IStrategy(ABC, HyperStrategyMixin):
else: else:
raise StrategyError(message) raise StrategyError(message)
def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool]: def get_signal(
self,
pair: str,
timeframe: str,
dataframe: DataFrame
) -> Tuple[bool, bool, Optional[str]]:
""" """
Calculates current signal based based on the buy / sell columns of the dataframe. Calculates current signal based based on the buy / sell columns of the dataframe.
Used by Bot to get the signal to buy or sell Used by Bot to get the signal to buy or sell
@ -507,7 +513,7 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
if not isinstance(dataframe, DataFrame) or dataframe.empty: if not isinstance(dataframe, DataFrame) or dataframe.empty:
logger.warning(f'Empty candle (OHLCV) data for pair {pair}') logger.warning(f'Empty candle (OHLCV) data for pair {pair}')
return False, False return False, False, None
latest_date = dataframe['date'].max() latest_date = dataframe['date'].max()
latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1] latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
@ -522,9 +528,12 @@ class IStrategy(ABC, HyperStrategyMixin):
'Outdated history for pair %s. Last tick is %s minutes old', 'Outdated history for pair %s. Last tick is %s minutes old',
pair, int((arrow.utcnow() - latest_date).total_seconds() // 60) pair, int((arrow.utcnow() - latest_date).total_seconds() // 60)
) )
return False, False return False, False, None
buy = latest[SignalType.BUY.value] == 1
sell = latest[SignalType.SELL.value] == 1
buy_tag = latest.get(SignalTagType.BUY_TAG.value, None)
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
latest['date'], pair, str(buy), str(sell)) latest['date'], pair, str(buy), str(sell))
timeframe_seconds = timeframe_to_seconds(timeframe) timeframe_seconds = timeframe_to_seconds(timeframe)
@ -532,8 +541,8 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time=datetime.now(timezone.utc), current_time=datetime.now(timezone.utc),
timeframe_seconds=timeframe_seconds, timeframe_seconds=timeframe_seconds,
buy=buy): buy=buy):
return False, sell return False, sell, buy_tag
return buy, sell return buy, sell, buy_tag
def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, def ignore_expired_candle(self, latest_date: datetime, current_time: datetime,
timeframe_seconds: int, buy: bool): timeframe_seconds: int, buy: bool):

View File

@ -182,7 +182,7 @@ def get_patched_worker(mocker, config) -> Worker:
return Worker(args=None, config=config) return Worker(args=None, config=config)
def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None)) -> None:
""" """
:param mocker: mocker to patch IStrategy class :param mocker: mocker to patch IStrategy class
:param value: which value IStrategy.get_signal() must return :param value: which value IStrategy.get_signal() must return

View File

@ -29,7 +29,6 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
tests_start_time = arrow.get(2018, 10, 3) tests_start_time = arrow.get(2018, 10, 3)
timeframe_in_minute = 60 timeframe_in_minute = 60
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
# Helpers for this test file # Helpers for this test file

View File

@ -18,6 +18,7 @@ class BTrade(NamedTuple):
sell_reason: SellType sell_reason: SellType
open_tick: int open_tick: int
close_tick: int close_tick: int
buy_tag: Optional[str] = None
class BTContainer(NamedTuple): class BTContainer(NamedTuple):
@ -44,6 +45,7 @@ def _get_frame_time_from_offset(offset):
def _build_backtest_dataframe(data): def _build_backtest_dataframe(data):
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell'] columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell']
columns = columns + ['buy_tag'] if len(data[0]) == 9 else columns
frame = DataFrame.from_records(data, columns=columns) frame = DataFrame.from_records(data, columns=columns)
frame['date'] = frame['date'].apply(_get_frame_time_from_offset) frame['date'] = frame['date'].apply(_get_frame_time_from_offset)

View File

@ -516,6 +516,26 @@ tc32 = BTContainer(data=[
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
) )
# Test 33: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 1%, ROI: 10% (should not apply)
tc33 = BTContainer(data=[
# D O H L C V B S BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'],
[1, 5000, 5500, 5000, 4900, 6172, 0, 0, None], # enter trade (signal on last candle) and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, None],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, None],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, None]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(
sell_reason=SellType.TRAILING_STOP_LOSS,
open_tick=1,
close_tick=1,
buy_tag='buy_signal_01'
)]
)
TESTS = [ TESTS = [
tc0, tc0,
tc1, tc1,
@ -550,6 +570,7 @@ TESTS = [
tc30, tc30,
tc31, tc31,
tc32, tc32,
tc33,
] ]
@ -599,5 +620,6 @@ 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.sell_reason == trade.sell_reason.value
assert res.buy_tag == trade.buy_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)

View File

@ -496,6 +496,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
0, # Sell 0, # Sell
0.00099, # Low 0.00099, # Low
0.0012, # High 0.0012, # High
'', # Buy Signal Name
] ]
trade = backtesting._enter_trade(pair, row=row) trade = backtesting._enter_trade(pair, row=row)
assert isinstance(trade, LocalTrade) assert isinstance(trade, LocalTrade)
@ -583,6 +584,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'min_rate': [0.1038, 0.10302485], 'min_rate': [0.1038, 0.10302485],
'max_rate': [0.10501, 0.1038888], 'max_rate': [0.10501, 0.1038888],
'is_open': [False, False], 'is_open': [False, False],
'buy_tag': [None, None],
}) })
pd.testing.assert_frame_equal(results, expected) pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair] data_pair = processed[pair]
@ -858,7 +860,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'locks': [], 'locks': [],
'rejected_signals': 20, 'rejected_signals': 20,
'final_balance': 1000, 'final_balance': 1000,
}) })
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC'])) PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)

View File

@ -35,7 +35,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -69,6 +69,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'min_rate': ANY, 'min_rate': ANY,
'max_rate': ANY, 'max_rate': ANY,
'strategy': ANY, 'strategy': ANY,
'buy_tag': ANY,
'timeframe': 5, 'timeframe': 5,
'open_order_id': ANY, 'open_order_id': ANY,
'close_date': None, 'close_date': None,
@ -135,6 +136,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'min_rate': ANY, 'min_rate': ANY,
'max_rate': ANY, 'max_rate': ANY,
'strategy': ANY, 'strategy': ANY,
'buy_tag': ANY,
'timeframe': ANY, 'timeframe': ANY,
'open_order_id': ANY, 'open_order_id': ANY,
'close_date': None, 'close_date': None,
@ -190,7 +192,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
) )
del default_conf['fiat_display_currency'] del default_conf['fiat_display_currency']
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -237,7 +239,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
stake_currency = default_conf['stake_currency'] stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency'] fiat_display_currency = default_conf['fiat_display_currency']
@ -369,7 +371,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
stake_currency = default_conf['stake_currency'] stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency'] fiat_display_currency = default_conf['fiat_display_currency']
@ -457,7 +459,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
stake_currency = default_conf['stake_currency'] stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency'] fiat_display_currency = default_conf['fiat_display_currency']
@ -524,7 +526,7 @@ def test_rpc_balance_handle_error(default_conf, mocker):
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter()
with pytest.raises(RPCException, match="Error getting current tickers."): with pytest.raises(RPCException, match="Error getting current tickers."):
@ -565,7 +567,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
) )
default_conf['dry_run'] = False default_conf['dry_run'] = False
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter()
@ -610,7 +612,7 @@ def test_rpc_start(mocker, default_conf) -> None:
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -631,7 +633,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -653,7 +655,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -685,7 +687,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000) mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000)
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -803,7 +805,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
@ -836,7 +838,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
counts = rpc._rpc_count() counts = rpc._rpc_count()
@ -861,7 +863,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
pair = 'ETH/BTC' pair = 'ETH/BTC'
trade = rpc._rpc_forcebuy(pair, None) trade = rpc._rpc_forcebuy(pair, None)
@ -887,7 +889,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
# Test not buying # Test not buying
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
freqtradebot.config['stake_amount'] = 0 freqtradebot.config['stake_amount'] = 0
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
pair = 'TKN/BTC' pair = 'TKN/BTC'
trade = rpc._rpc_forcebuy(pair, None) trade = rpc._rpc_forcebuy(pair, None)
@ -900,7 +902,7 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
pair = 'ETH/BTC' pair = 'ETH/BTC'
with pytest.raises(RPCException, match=r'trader is not running'): with pytest.raises(RPCException, match=r'trader is not running'):
@ -911,7 +913,7 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
pair = 'ETH/BTC' pair = 'ETH/BTC'
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'): with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):

View File

@ -442,7 +442,7 @@ def test_api_balance(botclient, mocker, rpc_balance):
def test_api_count(botclient, mocker, ticker, fee, markets): def test_api_count(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
@ -504,7 +504,7 @@ def test_api_locks(botclient):
def test_api_show_config(botclient, mocker): def test_api_show_config(botclient, mocker):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
rc = client_get(client, f"{BASE_URI}/show_config") rc = client_get(client, f"{BASE_URI}/show_config")
assert_response(rc) assert_response(rc)
@ -522,7 +522,7 @@ def test_api_show_config(botclient, mocker):
def test_api_daily(botclient, mocker, ticker, fee, markets): def test_api_daily(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
@ -540,7 +540,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
def test_api_trades(botclient, mocker, fee, markets): def test_api_trades(botclient, mocker, fee, markets):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets) markets=PropertyMock(return_value=markets)
@ -568,7 +568,7 @@ def test_api_trades(botclient, mocker, fee, markets):
def test_api_trade_single(botclient, mocker, fee, ticker, markets): def test_api_trade_single(botclient, mocker, fee, ticker, markets):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets), markets=PropertyMock(return_value=markets),
@ -588,7 +588,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
def test_api_delete_trade(botclient, mocker, fee, markets): def test_api_delete_trade(botclient, mocker, fee, markets):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
stoploss_mock = MagicMock() stoploss_mock = MagicMock()
cancel_mock = MagicMock() cancel_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -662,7 +662,7 @@ def test_api_logs(botclient):
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
@ -678,7 +678,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_api_profit(botclient, mocker, ticker, fee, markets): def test_api_profit(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
@ -729,7 +729,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_api_stats(botclient, mocker, ticker, fee, markets,): def test_api_stats(botclient, mocker, ticker, fee, markets,):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
@ -757,7 +757,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
def test_api_performance(botclient, fee): def test_api_performance(botclient, fee):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -803,7 +803,7 @@ def test_api_performance(botclient, fee):
def test_api_status(botclient, mocker, ticker, fee, markets): def test_api_status(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
@ -875,6 +875,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'sell_reason': None, 'sell_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': 'DefaultStrategy', 'strategy': 'DefaultStrategy',
'buy_tag': None,
'timeframe': 5, 'timeframe': 5,
'exchange': 'binance', 'exchange': 'binance',
} }
@ -1029,6 +1030,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'sell_reason': None, 'sell_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': 'DefaultStrategy', 'strategy': 'DefaultStrategy',
'buy_tag': None,
'timeframe': 5, 'timeframe': 5,
'exchange': 'binance', 'exchange': 'binance',
} }
@ -1044,7 +1046,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets):
markets=PropertyMock(return_value=markets), markets=PropertyMock(return_value=markets),
_is_dry_limit_order_filled=MagicMock(return_value=False), _is_dry_limit_order_filled=MagicMock(return_value=False),
) )
patch_get_signal(ftbot, (True, False)) patch_get_signal(ftbot, (True, False, None))
rc = client_post(client, f"{BASE_URI}/forcesell", rc = client_post(client, f"{BASE_URI}/forcesell",
data='{"tradeid": "1"}') data='{"tradeid": "1"}')
@ -1185,8 +1187,10 @@ def test_api_plot_config(botclient):
assert_response(rc) assert_response(rc)
assert rc.json() == {} assert rc.json() == {}
ftbot.strategy.plot_config = {'main_plot': {'sma': {}}, ftbot.strategy.plot_config = {
'subplots': {'RSI': {'rsi': {'color': 'red'}}}} 'main_plot': {'sma': {}},
'subplots': {'RSI': {'rsi': {'color': 'red'}}}
}
rc = client_get(client, f"{BASE_URI}/plot_config") rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc) assert_response(rc)
assert rc.json() == ftbot.strategy.plot_config assert rc.json() == ftbot.strategy.plot_config

View File

@ -119,7 +119,7 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None:
rpc = RPC(bot) rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf) dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False)) patch_get_signal(bot, (True, False, None))
dummy.dummy_handler(update=update, context=MagicMock()) dummy.dummy_handler(update=update, context=MagicMock())
assert dummy.state['called'] is True assert dummy.state['called'] is True
assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog) assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog)
@ -139,7 +139,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
rpc = RPC(bot) rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf) dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False)) patch_get_signal(bot, (True, False, None))
dummy.dummy_handler(update=update, context=MagicMock()) dummy.dummy_handler(update=update, context=MagicMock())
assert dummy.state['called'] is False assert dummy.state['called'] is False
assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog) assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog)
@ -155,7 +155,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None
bot = FreqtradeBot(default_conf) bot = FreqtradeBot(default_conf)
rpc = RPC(bot) rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf) dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False)) patch_get_signal(bot, (True, False, None))
dummy.dummy_exception(update=update, context=MagicMock()) dummy.dummy_exception(update=update, context=MagicMock())
assert dummy.state['called'] is False assert dummy.state['called'] is False
@ -185,6 +185,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'current_rate': 1.098e-05, 'current_rate': 1.098e-05,
'amount': 90.99181074, 'amount': 90.99181074,
'stake_amount': 90.99181074, 'stake_amount': 90.99181074,
'buy_tag': None,
'close_profit_pct': None, 'close_profit_pct': None,
'profit': -0.0059, 'profit': -0.0059,
'profit_pct': -0.59, 'profit_pct': -0.59,
@ -228,7 +229,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
# Status is also enabled when stopped # Status is also enabled when stopped
@ -285,7 +286,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
# Status table is also enabled when stopped # Status table is also enabled when stopped
@ -329,7 +330,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
@ -400,7 +401,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
# Try invalid data # Try invalid data
msg_mock.reset_mock() msg_mock.reset_mock()
@ -432,7 +433,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
telegram._profit(update=update, context=MagicMock()) telegram._profit(update=update, context=MagicMock())
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
@ -487,7 +488,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
telegram._stats(update=update, context=MagicMock()) telegram._stats(update=update, context=MagicMock())
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
@ -513,7 +514,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
side_effect=lambda a, b: f"{a}/{b}") side_effect=lambda a, b: f"{a}/{b}")
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
telegram._balance(update=update, context=MagicMock()) telegram._balance(update=update, context=MagicMock())
result = msg_mock.call_args_list[0][0][0] result = msg_mock.call_args_list[0][0][0]
@ -536,7 +537,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
freqtradebot.config['dry_run'] = False freqtradebot.config['dry_run'] = False
telegram._balance(update=update, context=MagicMock()) telegram._balance(update=update, context=MagicMock())
@ -549,7 +550,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
telegram._balance(update=update, context=MagicMock()) telegram._balance(update=update, context=MagicMock())
result = msg_mock.call_args_list[0][0][0] result = msg_mock.call_args_list[0][0][0]
@ -578,7 +579,7 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
}) })
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
telegram._balance(update=update, context=MagicMock()) telegram._balance(update=update, context=MagicMock())
assert msg_mock.call_count > 1 assert msg_mock.call_count > 1
@ -677,7 +678,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf) telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
@ -736,7 +737,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf) telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
@ -797,7 +798,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf) telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
@ -838,7 +839,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
return_value=15000.0) return_value=15000.0)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
# Trader is not running # Trader is not running
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -876,7 +877,7 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
# /forcebuy ETH/BTC # /forcebuy ETH/BTC
context = MagicMock() context = MagicMock()
@ -905,7 +906,7 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
update.message.text = '/forcebuy ETH/Nonepair' update.message.text = '/forcebuy ETH/Nonepair'
telegram._forcebuy(update=update, context=MagicMock()) telegram._forcebuy(update=update, context=MagicMock())
@ -922,7 +923,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
context = MagicMock() context = MagicMock()
context.args = [] context.args = []
@ -950,7 +951,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
@ -978,7 +979,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
telegram._count(update=update, context=MagicMock()) telegram._count(update=update, context=MagicMock())
@ -1007,7 +1008,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False, None))
telegram._locks(update=update, context=MagicMock()) telegram._locks(update=update, context=MagicMock())
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'No active locks.' in msg_mock.call_args_list[0][0][0] assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
@ -1253,6 +1254,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
msg = { msg = {
'type': RPCMessageType.BUY, 'type': RPCMessageType.BUY,
'trade_id': 1, 'trade_id': 1,
'buy_tag': 'buy_signal_01',
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'limit': 1.099e-05, 'limit': 1.099e-05,
@ -1270,6 +1272,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
telegram.send_msg(msg) telegram.send_msg(msg)
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \ == '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
'*Buy Tag:* `buy_signal_01`\n' \
'*Amount:* `1333.33333333`\n' \ '*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \ '*Open Rate:* `0.00001099`\n' \
'*Current Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \
@ -1297,6 +1300,7 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY_CANCEL, 'type': RPCMessageType.BUY_CANCEL,
'buy_tag': 'buy_signal_01',
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
@ -1314,6 +1318,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY_FILL, 'type': RPCMessageType.BUY_FILL,
'buy_tag': 'buy_signal_01',
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/USDT', 'pair': 'ETH/USDT',
@ -1498,6 +1503,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY, 'type': RPCMessageType.BUY,
'buy_tag': 'buy_signal_01',
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
@ -1512,6 +1518,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'open_date': arrow.utcnow().shift(hours=-1) 'open_date': arrow.utcnow().shift(hours=-1)
}) })
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
'*Buy Tag:* `buy_signal_01`\n'
'*Amount:* `1333.33333333`\n' '*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00001099`\n' '*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n' '*Current Rate:* `0.00001099`\n'

View File

@ -38,15 +38,20 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history):
mocked_history['buy'] = 0 mocked_history['buy'] = 0
mocked_history.loc[1, 'sell'] = 1 mocked_history.loc[1, 'sell'] = 1
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True) assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None)
mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 1 mocked_history.loc[1, 'buy'] = 1
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False) assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None)
mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 0 mocked_history.loc[1, 'buy'] = 0
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False) assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 1
mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01'
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01')
def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
@ -63,15 +68,21 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
def test_get_signal_empty(default_conf, mocker, caplog): def test_get_signal_empty(default_conf, mocker, caplog):
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) assert (False, False, None) == _STRATEGY.get_signal(
'foo', default_conf['timeframe'], DataFrame()
)
assert log_has('Empty candle (OHLCV) data for pair foo', caplog) assert log_has('Empty candle (OHLCV) data for pair foo', caplog)
caplog.clear() caplog.clear()
assert (False, False) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) assert (False, False, None) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None)
assert log_has('Empty candle (OHLCV) data for pair bar', caplog) assert log_has('Empty candle (OHLCV) data for pair bar', caplog)
caplog.clear() caplog.clear()
assert (False, False) == _STRATEGY.get_signal('baz', default_conf['timeframe'], DataFrame([])) assert (False, False, None) == _STRATEGY.get_signal(
'baz',
default_conf['timeframe'],
DataFrame([])
)
assert log_has('Empty candle (OHLCV) data for pair baz', caplog) assert log_has('Empty candle (OHLCV) data for pair baz', caplog)
@ -107,7 +118,11 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch.object(_STRATEGY, 'assert_df') mocker.patch.object(_STRATEGY, 'assert_df')
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'], mocked_history) assert (False, False, None) == _STRATEGY.get_signal(
'xyz',
default_conf['timeframe'],
mocked_history
)
assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog)

View File

@ -536,7 +536,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
) )
default_conf['stake_amount'] = 10 default_conf['stake_amount'] = 10
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade, value=(False, False)) patch_get_signal(freqtrade, value=(False, False, None))
Trade.query = MagicMock() Trade.query = MagicMock()
Trade.query.filter = MagicMock() Trade.query.filter = MagicMock()
@ -757,7 +757,10 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
refresh_latest_ohlcv=refresh_mock, refresh_latest_ohlcv=refresh_mock,
) )
inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")]) inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")])
mocker.patch('freqtrade.strategy.interface.IStrategy.get_signal', return_value=(False, False)) mocker.patch(
'freqtrade.strategy.interface.IStrategy.get_signal',
return_value=(False, False, '')
)
mocker.patch('time.sleep', return_value=None) mocker.patch('time.sleep', return_value=None)
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
@ -1857,7 +1860,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order_open, limi
assert trade.is_open is True assert trade.is_open is True
freqtrade.wallets.update() freqtrade.wallets.update()
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.open_order_id == limit_sell_order['id'] assert trade.open_order_id == limit_sell_order['id']
@ -1882,7 +1885,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
) )
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade, value=(True, True)) patch_get_signal(freqtrade, value=(True, True, None))
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions() freqtrade.enter_positions()
@ -1893,7 +1896,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
assert nb_trades == 0 assert nb_trades == 0
# Buy is triggering, so buying ... # Buy is triggering, so buying ...
patch_get_signal(freqtrade, value=(True, False)) patch_get_signal(freqtrade, value=(True, False, None))
freqtrade.enter_positions() freqtrade.enter_positions()
trades = Trade.query.all() trades = Trade.query.all()
nb_trades = len(trades) nb_trades = len(trades)
@ -1901,7 +1904,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
assert trades[0].is_open is True assert trades[0].is_open is True
# Buy and Sell are not triggering, so doing nothing ... # Buy and Sell are not triggering, so doing nothing ...
patch_get_signal(freqtrade, value=(False, False)) patch_get_signal(freqtrade, value=(False, False, None))
assert freqtrade.handle_trade(trades[0]) is False assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all() trades = Trade.query.all()
nb_trades = len(trades) nb_trades = len(trades)
@ -1909,7 +1912,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
assert trades[0].is_open is True assert trades[0].is_open is True
# Buy and Sell are triggering, so doing nothing ... # Buy and Sell are triggering, so doing nothing ...
patch_get_signal(freqtrade, value=(True, True)) patch_get_signal(freqtrade, value=(True, True, None))
assert freqtrade.handle_trade(trades[0]) is False assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all() trades = Trade.query.all()
nb_trades = len(trades) nb_trades = len(trades)
@ -1917,7 +1920,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
assert trades[0].is_open is True assert trades[0].is_open is True
# Sell is triggering, guess what : we are Selling! # Sell is triggering, guess what : we are Selling!
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
trades = Trade.query.all() trades = Trade.query.all()
assert freqtrade.handle_trade(trades[0]) is True assert freqtrade.handle_trade(trades[0]) is True
@ -1935,7 +1938,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtrade, value=(True, False)) patch_get_signal(freqtrade, value=(True, False, None))
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions() freqtrade.enter_positions()
@ -1948,7 +1951,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
# we might just want to check if we are in a sell condition without # we might just want to check if we are in a sell condition without
# executing # executing
# if ROI is reached we must sell # if ROI is reached we must sell
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) assert freqtrade.handle_trade(trade)
assert log_has("ETH/BTC - Required profit reached. sell_type=SellType.ROI", assert log_has("ETH/BTC - Required profit reached. sell_type=SellType.ROI",
caplog) caplog)
@ -1974,10 +1977,10 @@ def test_handle_trade_use_sell_signal(
trade = Trade.query.first() trade = Trade.query.first()
trade.is_open = True trade.is_open = True
patch_get_signal(freqtrade, value=(False, False)) patch_get_signal(freqtrade, value=(False, False, None))
assert not freqtrade.handle_trade(trade) assert not freqtrade.handle_trade(trade)
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) assert freqtrade.handle_trade(trade)
assert log_has("ETH/BTC - Sell signal received. sell_type=SellType.SELL_SIGNAL", assert log_has("ETH/BTC - Sell signal received. sell_type=SellType.SELL_SIGNAL",
caplog) caplog)
@ -3013,7 +3016,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
freqtrade.wallets.update() freqtrade.wallets.update()
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
freqtrade.strategy.sell_profit_offset = 0.0 freqtrade.strategy.sell_profit_offset = 0.0
@ -3048,7 +3051,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
freqtrade.wallets.update() freqtrade.wallets.update()
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.SELL_SIGNAL.value assert trade.sell_reason == SellType.SELL_SIGNAL.value
@ -3079,7 +3082,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -3111,7 +3114,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
freqtrade.wallets.update() freqtrade.wallets.update()
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.SELL_SIGNAL.value assert trade.sell_reason == SellType.SELL_SIGNAL.value
@ -3140,7 +3143,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
trade = Trade.query.first() trade = Trade.query.first()
amnt = trade.amount amnt = trade.amount
trade.update(limit_buy_order) trade.update(limit_buy_order)
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985)) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985))
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
@ -3259,11 +3262,11 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
freqtrade.wallets.update() freqtrade.wallets.update()
patch_get_signal(freqtrade, value=(True, True)) patch_get_signal(freqtrade, value=(True, True, None))
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
# Test if buy-signal is absent (should sell due to roi = true) # Test if buy-signal is absent (should sell due to roi = true)
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.ROI.value assert trade.sell_reason == SellType.ROI.value
@ -3524,11 +3527,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
# Sell due to min_roi_reached # Sell due to min_roi_reached
patch_get_signal(freqtrade, value=(True, True)) patch_get_signal(freqtrade, value=(True, True, None))
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
# Test if buy-signal is absent # Test if buy-signal is absent
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.SELL_SIGNAL.value assert trade.sell_reason == SellType.SELL_SIGNAL.value
@ -4056,7 +4059,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order_open, limit_buy_o
freqtrade.wallets.update() freqtrade.wallets.update()
assert trade.is_open is True assert trade.is_open is True
patch_get_signal(freqtrade, value=(False, True)) patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0] assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0]

View File

@ -861,6 +861,7 @@ def test_to_json(default_conf, fee):
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=arrow.utcnow().shift(hours=-2).datetime,
open_rate=0.123, open_rate=0.123,
exchange='binance', exchange='binance',
buy_tag=None,
open_order_id='dry_run_buy_12345' open_order_id='dry_run_buy_12345'
) )
result = trade.to_json() result = trade.to_json()
@ -910,6 +911,7 @@ def test_to_json(default_conf, fee):
'min_rate': None, 'min_rate': None,
'max_rate': None, 'max_rate': None,
'strategy': None, 'strategy': None,
'buy_tag': None,
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
} }
@ -926,6 +928,7 @@ def test_to_json(default_conf, fee):
close_date=arrow.utcnow().shift(hours=-1).datetime, close_date=arrow.utcnow().shift(hours=-1).datetime,
open_rate=0.123, open_rate=0.123,
close_rate=0.125, close_rate=0.125,
buy_tag='buys_signal_001',
exchange='binance', exchange='binance',
) )
result = trade.to_json() result = trade.to_json()
@ -975,6 +978,7 @@ def test_to_json(default_conf, fee):
'sell_reason': None, 'sell_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': None, 'strategy': None,
'buy_tag': 'buys_signal_001',
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
} }
@ -1319,7 +1323,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',
) )
# Parent (LocalTrade) should have the same attributes # Parent (LocalTrade) should have the same attributes
for item in trade: for item in trade: