Merge pull request #6306 from freqtrade/short_forceentry

add `/forcelong` and `/forceshort` commands
This commit is contained in:
Matthias 2022-01-30 07:36:14 +01:00 committed by GitHub
commit 000b8ff281
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 190 additions and 103 deletions

View File

@ -148,6 +148,7 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
| `forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`). | `forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
| `forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`). | `forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
| `forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True) | `forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
| `forceenter <pair> <side> [rate]` | Instantly longs or shorts the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
| `performance` | Show performance of each finished trade grouped by pair. | `performance` | Show performance of each finished trade grouped by pair.
| `balance` | Show account balance per currency. | `balance` | Show account balance per currency.
| `daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7). | `daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7).
@ -215,6 +216,13 @@ forcebuy
:param pair: Pair to buy (ETH/BTC) :param pair: Pair to buy (ETH/BTC)
:param price: Optional - price to buy :param price: Optional - price to buy
forceenter
Force entering a trade
:param pair: Pair to buy (ETH/BTC)
:param side: 'long' or 'short'
:param price: Optional - price to buy
forcesell forcesell
Force-sell a trade. Force-sell a trade.
@ -285,6 +293,9 @@ strategy
:param strategy: Strategy class name :param strategy: Strategy class name
sysinfo
Provides system information (CPU, RAM usage)
trade trade
Return specific trade Return specific trade

View File

@ -173,7 +173,8 @@ official commands. You can ask at any moment for help with `/help`.
| `/profit [<n>]` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default) | `/profit [<n>]` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default)
| `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`). | `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`). | `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
| `/forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`forcebuy_enable` must be set to True) | `/forcelong <pair> [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`forcebuy_enable` must be set to True)
| `/forceshort <pair> [rate]` | Instantly shorts the given pair. Rate is optional and only applies to limit orders. This will only work on non-spot markets. (`forcebuy_enable` must be set to True)
| `/performance` | Show performance of each finished trade grouped by pair | `/performance` | Show performance of each finished trade grouped by pair
| `/balance` | Show account balance per currency | `/balance` | Show account balance per currency
| `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7) | `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7)
@ -275,11 +276,13 @@ Starting capital is either taken from the `available_capital` setting, or calcul
> **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)` > **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)`
### /forcebuy <pair> [rate] ### /forcelong <pair> [rate] | /forceshort <pair> [rate]
> **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) `/forcebuy <pair> [rate]` is also supported for longs but should be considered deprecated.
Omitting the pair will open a query asking for the pair to buy (based on the current whitelist). > **BITTREX:** Long ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`)
Omitting the pair will open a query asking for the pair to trade (based on the current whitelist).
![Telegram force-buy screenshot](assets/telegram_forcebuy.png) ![Telegram force-buy screenshot](assets/telegram_forcebuy.png)

View File

@ -19,6 +19,6 @@ class SignalTagType(Enum):
EXIT_TAG = "exit_tag" EXIT_TAG = "exit_tag"
class SignalDirection(Enum): class SignalDirection(str, Enum):
LONG = 'long' LONG = 'long'
SHORT = 'short' SHORT = 'short'

View File

@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel from pydantic import BaseModel
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.enums import OrderTypeValues from freqtrade.enums import OrderTypeValues, SignalDirection
class Ping(BaseModel): class Ping(BaseModel):
@ -249,7 +249,7 @@ class TradeResponse(BaseModel):
total_trades: int total_trades: int
class ForceBuyResponse(BaseModel): class ForceEnterResponse(BaseModel):
__root__: Union[TradeSchema, StatusMsg] __root__: Union[TradeSchema, StatusMsg]
@ -279,14 +279,15 @@ class Logs(BaseModel):
logs: List[List] logs: List[List]
class ForceBuyPayload(BaseModel): class ForceEnterPayload(BaseModel):
pair: str pair: str
side: SignalDirection = SignalDirection.LONG
price: Optional[float] price: Optional[float]
ordertype: Optional[OrderTypeValues] ordertype: Optional[OrderTypeValues]
stakeamount: Optional[float] stakeamount: Optional[float]
class ForceSellPayload(BaseModel): class ForceExitPayload(BaseModel):
tradeid: str tradeid: str
ordertype: Optional[OrderTypeValues] ordertype: Optional[OrderTypeValues]

View File

@ -14,8 +14,8 @@ from freqtrade.exceptions import OperationalException
from freqtrade.rpc import RPC from freqtrade.rpc import RPC
from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload,
BlacklistResponse, Count, Daily, BlacklistResponse, Count, Daily,
DeleteLockRequest, DeleteTrade, ForceBuyPayload, DeleteLockRequest, DeleteTrade, ForceEnterPayload,
ForceBuyResponse, ForceSellPayload, Locks, Logs, ForceEnterResponse, ForceExitPayload, Locks, Logs,
OpenTradeSchema, PairHistory, PerformanceEntry, OpenTradeSchema, PairHistory, PerformanceEntry,
Ping, PlotConfig, Profit, ResultMsg, ShowConfig, Ping, PlotConfig, Profit, ResultMsg, ShowConfig,
Stats, StatusMsg, StrategyListResponse, Stats, StatusMsg, StrategyListResponse,
@ -33,7 +33,9 @@ logger = logging.getLogger(__name__)
# 1.11: forcebuy and forcesell accept ordertype # 1.11: forcebuy and forcesell accept ordertype
# 1.12: add blacklist delete endpoint # 1.12: add blacklist delete endpoint
# 1.13: forcebuy supports stake_amount # 1.13: forcebuy supports stake_amount
API_VERSION = 1.13 # versions 2.xx -> futures/short branch
# 2.13: addition of Forceenter
API_VERSION = 2.13
# Public API, requires no auth. # Public API, requires no auth.
router_public = APIRouter() router_public = APIRouter()
@ -133,23 +135,28 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g
return resp return resp
@router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading']) # /forcebuy is deprecated with short addition. use ForceEntry instead
def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): @router.post('/forceenter', response_model=ForceEnterResponse, tags=['trading'])
@router.post('/forcebuy', response_model=ForceEnterResponse, tags=['trading'])
def forceentry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None ordertype = payload.ordertype.value if payload.ordertype else None
stake_amount = payload.stakeamount if payload.stakeamount else None stake_amount = payload.stakeamount if payload.stakeamount else None
trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount) trade = rpc._rpc_force_entry(payload.pair, payload.price, order_side=payload.side,
order_type=ordertype, stake_amount=stake_amount)
if trade: if trade:
return ForceBuyResponse.parse_obj(trade.to_json()) return ForceEnterResponse.parse_obj(trade.to_json())
else: else:
return ForceBuyResponse.parse_obj({"status": f"Error buying pair {payload.pair}."}) return ForceEnterResponse.parse_obj(
{"status": f"Error entering {payload.side} trade for pair {payload.pair}."})
@router.post('/forceexit', response_model=ResultMsg, tags=['trading'])
@router.post('/forcesell', response_model=ResultMsg, tags=['trading']) @router.post('/forcesell', response_model=ResultMsg, tags=['trading'])
def forcesell(payload: ForceSellPayload, rpc: RPC = Depends(get_rpc)): def forcesell(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None ordertype = payload.ordertype.value if payload.ordertype else None
return rpc._rpc_forcesell(payload.tradeid, ordertype) return rpc._rpc_forceexit(payload.tradeid, ordertype)
@router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) @router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])

View File

@ -18,6 +18,8 @@ from freqtrade.configuration.timerange import TimeRange
from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT
from freqtrade.data.history import load_data from freqtrade.data.history import load_data
from freqtrade.enums import SellType, State from freqtrade.enums import SellType, State
from freqtrade.enums.signaltype import SignalDirection
from freqtrade.enums.tradingmode import TradingMode
from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exceptions import ExchangeError, PricingError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
from freqtrade.loggers import bufferHandler from freqtrade.loggers import bufferHandler
@ -662,7 +664,7 @@ class RPC:
return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} return {'status': 'No more buy will occur from now. Run /reload_config to reset.'}
def _rpc_forcesell(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: def _rpc_forceexit(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]:
""" """
Handler for forcesell <id>. Handler for forcesell <id>.
Sells the given trade at current price Sells the given trade at current price
@ -719,7 +721,9 @@ class RPC:
self._freqtrade.wallets.update() self._freqtrade.wallets.update()
return {'result': f'Created sell order for trade {trade_id}.'} return {'result': f'Created sell order for trade {trade_id}.'}
def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None, def _rpc_force_entry(self, pair: str, price: Optional[float], *,
order_type: Optional[str] = None,
order_side: SignalDirection = SignalDirection.LONG,
stake_amount: Optional[float] = None) -> Optional[Trade]: stake_amount: Optional[float] = None) -> Optional[Trade]:
""" """
Handler for forcebuy <asset> <price> Handler for forcebuy <asset> <price>
@ -727,11 +731,14 @@ class RPC:
""" """
if not self._freqtrade.config.get('forcebuy_enable', False): if not self._freqtrade.config.get('forcebuy_enable', False):
raise RPCException('Forcebuy not enabled.') raise RPCException('Forceentry not enabled.')
if self._freqtrade.state != State.RUNNING: if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running') raise RPCException('trader is not running')
if order_side == SignalDirection.SHORT and self._freqtrade.trading_mode == TradingMode.SPOT:
raise RPCException("Can't go short on Spot markets.")
# Check if pair quote currency equals to the stake currency. # Check if pair quote currency equals to the stake currency.
stake_currency = self._freqtrade.config.get('stake_currency') stake_currency = self._freqtrade.config.get('stake_currency')
if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency:
@ -754,7 +761,9 @@ class RPC:
order_type = self._freqtrade.strategy.order_types.get( order_type = self._freqtrade.strategy.order_types.get(
'forcebuy', self._freqtrade.strategy.order_types['buy']) 'forcebuy', self._freqtrade.strategy.order_types['buy'])
if self._freqtrade.execute_entry(pair, stake_amount, price, if self._freqtrade.execute_entry(pair, stake_amount, price,
ordertype=order_type, trade=trade): ordertype=order_type, trade=trade,
is_short=(order_side == SignalDirection.SHORT)
):
Trade.commit() Trade.commit()
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
return trade return trade

View File

@ -7,6 +7,7 @@ import json
import logging import logging
import re import re
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from functools import partial
from html import escape from html import escape
from itertools import chain from itertools import chain
from math import isnan from math import isnan
@ -23,6 +24,8 @@ from telegram.utils.helpers import escape_markdown
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.constants import DUST_PER_COIN from freqtrade.constants import DUST_PER_COIN
from freqtrade.enums import RPCMessageType from freqtrade.enums import RPCMessageType
from freqtrade.enums.signaltype import SignalDirection
from freqtrade.enums.tradingmode import TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import chunks, plural, round_coin_value from freqtrade.misc import chunks, plural, round_coin_value
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
@ -113,7 +116,8 @@ class Telegram(RPCHandler):
r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/stopbuy$', r'/reload_config$', r'/show_config$',
r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$',
r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$',
r'/forcebuy$', r'/edge$', r'/help$', r'/version$'] r'/forcebuy$', r'/forcelong$', r'/forceshort$',
r'/edge$', r'/help$', r'/version$']
# Create keys for generation # Create keys for generation
valid_keys_print = [k.replace('$', '') for k in valid_keys] valid_keys_print = [k.replace('$', '') for k in valid_keys]
@ -150,8 +154,11 @@ class Telegram(RPCHandler):
CommandHandler('balance', self._balance), CommandHandler('balance', self._balance),
CommandHandler('start', self._start), CommandHandler('start', self._start),
CommandHandler('stop', self._stop), CommandHandler('stop', self._stop),
CommandHandler('forcesell', self._forcesell), CommandHandler(['forcesell', 'forceexit'], self._forceexit),
CommandHandler('forcebuy', self._forcebuy), CommandHandler(['forcebuy', 'forcelong'], partial(
self._forceenter, order_side=SignalDirection.LONG)),
CommandHandler('forceshort', partial(
self._forceenter, order_side=SignalDirection.SHORT)),
CommandHandler('trades', self._trades), CommandHandler('trades', self._trades),
CommandHandler('delete', self._delete_trade), CommandHandler('delete', self._delete_trade),
CommandHandler('performance', self._performance), CommandHandler('performance', self._performance),
@ -190,7 +197,7 @@ class Telegram(RPCHandler):
pattern='update_sell_reason_performance'), pattern='update_sell_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._forceenter_inline),
] ]
for handle in handles: for handle in handles:
self._updater.dispatcher.add_handler(handle) self._updater.dispatcher.add_handler(handle)
@ -846,7 +853,7 @@ class Telegram(RPCHandler):
self._send_msg('Status: `{status}`'.format(**msg)) self._send_msg('Status: `{status}`'.format(**msg))
@authorized_only @authorized_only
def _forcesell(self, update: Update, context: CallbackContext) -> None: def _forceexit(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /forcesell <id>. Handler for /forcesell <id>.
Sells the given trade at current price Sells the given trade at current price
@ -860,25 +867,27 @@ class Telegram(RPCHandler):
self._send_msg("You must specify a trade-id or 'all'.") self._send_msg("You must specify a trade-id or 'all'.")
return return
try: try:
msg = self._rpc._rpc_forcesell(trade_id) msg = self._rpc._rpc_forceexit(trade_id)
self._send_msg('Forcesell Result: `{result}`'.format(**msg)) self._send_msg('Forcesell Result: `{result}`'.format(**msg))
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) self._send_msg(str(e))
def _forcebuy_action(self, pair, price=None): def _forceenter_action(self, pair, price: Optional[float], order_side: SignalDirection):
try: try:
self._rpc._rpc_forcebuy(pair, price) self._rpc._rpc_force_entry(pair, price, order_side=order_side)
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) self._send_msg(str(e))
def _forcebuy_inline(self, update: Update, _: CallbackContext) -> None: def _forceenter_inline(self, update: Update, _: CallbackContext) -> None:
if update.callback_query: if update.callback_query:
query = update.callback_query query = update.callback_query
pair = query.data if query.data and '_||_' in query.data:
pair, side = query.data.split('_||_')
order_side = SignalDirection(side)
query.answer() query.answer()
query.edit_message_text(text=f"Force Buying: {pair}") query.edit_message_text(text=f"Manually entering {order_side} for {pair}")
self._forcebuy_action(pair) self._forceenter_action(pair, None, order_side)
@staticmethod @staticmethod
def _layout_inline_keyboard(buttons: List[InlineKeyboardButton], def _layout_inline_keyboard(buttons: List[InlineKeyboardButton],
@ -886,9 +895,10 @@ class Telegram(RPCHandler):
return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] return [buttons[i:i + cols] for i in range(0, len(buttons), cols)]
@authorized_only @authorized_only
def _forcebuy(self, update: Update, context: CallbackContext) -> None: def _forceenter(
self, update: Update, context: CallbackContext, order_side: SignalDirection) -> None:
""" """
Handler for /forcebuy <asset> <price>. Handler for /forcelong <asset> <price> and `/forceshort <asset> <price>
Buys a pair trade at the given or current price Buys a pair trade at the given or current price
:param bot: telegram bot :param bot: telegram bot
:param update: message update :param update: message update
@ -897,13 +907,18 @@ class Telegram(RPCHandler):
if context.args: if context.args:
pair = context.args[0] pair = context.args[0]
price = float(context.args[1]) if len(context.args) > 1 else None price = float(context.args[1]) if len(context.args) > 1 else None
self._forcebuy_action(pair, price) self._forceenter_action(pair, price, order_side)
else: else:
whitelist = self._rpc._rpc_whitelist()['whitelist'] whitelist = self._rpc._rpc_whitelist()['whitelist']
pairs = [InlineKeyboardButton(text=pair, callback_data=pair) for pair in whitelist] pair_buttons = [
InlineKeyboardButton(text=pair, callback_data=f"{pair}_||_{order_side}")
for pair in whitelist
]
self._send_msg(msg="Which pair?", self._send_msg(msg="Which pair?",
keyboard=self._layout_inline_keyboard(pairs)) keyboard=self._layout_inline_keyboard(pair_buttons),
callback_path="update_forcelong",
query=update.callback_query)
@authorized_only @authorized_only
def _trades(self, update: Update, context: CallbackContext) -> None: def _trades(self, update: Update, context: CallbackContext) -> None:
@ -1269,8 +1284,13 @@ class Telegram(RPCHandler):
:param update: message update :param update: message update
:return: None :return: None
""" """
forcebuy_text = ("*/forcebuy <pair> [<rate>]:* `Instantly buys the given pair. " forceenter_text = ("*/forcelong <pair> [<rate>]:* `Instantly buys the given pair. "
"Optionally takes a rate at which to buy " "Optionally takes a rate at which to buy "
"(only applies to limit orders).` \n"
)
if self._rpc._freqtrade.trading_mode != TradingMode.SPOT:
forceenter_text += ("*/forceshort <pair> [<rate>]:* `Instantly shorts the given pair. "
"Optionally takes a rate at which to sell "
"(only applies to limit orders).` \n") "(only applies to limit orders).` \n")
message = ( message = (
"_BotControl_\n" "_BotControl_\n"
@ -1278,9 +1298,9 @@ class Telegram(RPCHandler):
"*/start:* `Starts the trader`\n" "*/start:* `Starts the trader`\n"
"*/stop:* Stops the trader\n" "*/stop:* Stops the trader\n"
"*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n"
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " "*/forceexit <trade_id>|all:* `Instantly exits the given trade or all trades, "
"regardless of profit`\n" "regardless of profit`\n"
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" f"{forceenter_text if self._config.get('forcebuy_enable', False) else ''}"
"*/delete <trade_id>:* `Instantly delete the given trade in the database`\n" "*/delete <trade_id>:* `Instantly delete the given trade in the database`\n"
"*/whitelist:* `Show current whitelist` \n" "*/whitelist:* `Show current whitelist` \n"
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs "
@ -1374,6 +1394,7 @@ class Telegram(RPCHandler):
self._send_msg( self._send_msg(
f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
f"*Exchange:* `{val['exchange']}`\n" f"*Exchange:* `{val['exchange']}`\n"
f"*Market: * `{val['trading_mode']}`\n"
f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n" f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n"
f"*Max open Trades:* `{val['max_open_trades']}`\n" f"*Max open Trades:* `{val['max_open_trades']}`\n"
f"*Minimum ROI:* `{val['minimal_roi']}`\n" f"*Minimum ROI:* `{val['minimal_roi']}`\n"

View File

@ -261,6 +261,20 @@ class FtRestClient():
} }
return self._post("forcebuy", data=data) return self._post("forcebuy", data=data)
def forceenter(self, pair, side, price=None):
"""Force entering a trade
:param pair: Pair to buy (ETH/BTC)
:param side: 'long' or 'short'
:param price: Optional - price to buy
:return: json object of the trade
"""
data = {"pair": pair,
"side": side,
"price": price,
}
return self._post("forceenter", data=data)
def forcesell(self, tradeid): def forcesell(self, tradeid):
"""Force-sell a trade. """Force-sell a trade.

View File

@ -9,6 +9,7 @@ from numpy import isnan
from freqtrade.edge import PairInfo from freqtrade.edge import PairInfo
from freqtrade.enums import State, TradingMode from freqtrade.enums import State, TradingMode
from freqtrade.enums.signaltype import SignalDirection
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.persistence.pairlock_middleware import PairLocks from freqtrade.persistence.pairlock_middleware import PairLocks
@ -687,7 +688,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
assert freqtradebot.config['max_open_trades'] == 0 assert freqtradebot.config['max_open_trades'] == 0
def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: def test_rpc_forceexit(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
@ -714,29 +715,29 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'): with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell(None) rpc._rpc_forceexit(None)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*invalid argument*'): with pytest.raises(RPCException, match=r'.*invalid argument*'):
rpc._rpc_forcesell(None) rpc._rpc_forceexit(None)
msg = rpc._rpc_forcesell('all') msg = rpc._rpc_forceexit('all')
assert msg == {'result': 'Created sell orders for all open trades.'} assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.enter_positions() freqtradebot.enter_positions()
msg = rpc._rpc_forcesell('all') msg = rpc._rpc_forceexit('all')
assert msg == {'result': 'Created sell orders for all open trades.'} assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.enter_positions() freqtradebot.enter_positions()
msg = rpc._rpc_forcesell('2') msg = rpc._rpc_forceexit('2')
assert msg == {'result': 'Created sell order for trade 2.'} assert msg == {'result': 'Created sell order for trade 2.'}
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'): with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell(None) rpc._rpc_forceexit(None)
with pytest.raises(RPCException, match=r'.*trader is not running*'): with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell('all') rpc._rpc_forceexit('all')
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
@ -765,7 +766,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
) )
# check that the trade is called, which is done by ensuring exchange.cancel_order is called # check that the trade is called, which is done by ensuring exchange.cancel_order is called
# and trade amount is updated # and trade amount is updated
rpc._rpc_forcesell('3') rpc._rpc_forceexit('3')
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert trade.amount == filled_amount assert trade.amount == filled_amount
@ -793,7 +794,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
} }
) )
# check that the trade is called, which is done by ensuring exchange.cancel_order is called # check that the trade is called, which is done by ensuring exchange.cancel_order is called
msg = rpc._rpc_forcesell('4') msg = rpc._rpc_forceexit('4')
assert msg == {'result': 'Created sell order for trade 4.'} assert msg == {'result': 'Created sell order for trade 4.'}
assert cancel_order_mock.call_count == 2 assert cancel_order_mock.call_count == 2
assert trade.amount == amount assert trade.amount == amount
@ -810,7 +811,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
'filled': 0.0 'filled': 0.0
} }
) )
msg = rpc._rpc_forcesell('3') msg = rpc._rpc_forceexit('3')
assert msg == {'result': 'Created sell order for trade 3.'} assert msg == {'result': 'Created sell order for trade 3.'}
# status quo, no exchange calls # status quo, no exchange calls
assert cancel_order_mock.call_count == 3 assert cancel_order_mock.call_count == 3
@ -1090,7 +1091,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
assert counts["current"] == 1 assert counts["current"] == 1
def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None: def test_rpc_forceentry(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
default_conf['forcebuy_enable'] = True default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
buy_mm = MagicMock(return_value=limit_buy_order_open) buy_mm = MagicMock(return_value=limit_buy_order_open)
@ -1106,16 +1107,16 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
pair = 'ETH/BTC' pair = 'ETH/BTC'
trade = rpc._rpc_forcebuy(pair, None) trade = rpc._rpc_force_entry(pair, None)
assert isinstance(trade, Trade) assert isinstance(trade, Trade)
assert trade.pair == pair assert trade.pair == pair
assert trade.open_rate == ticker()['bid'] assert trade.open_rate == ticker()['bid']
# Test buy duplicate # Test buy duplicate
with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'): with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'):
rpc._rpc_forcebuy(pair, 0.0001) rpc._rpc_force_entry(pair, 0.0001)
pair = 'XRP/BTC' pair = 'XRP/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit') trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit')
assert isinstance(trade, Trade) assert isinstance(trade, Trade)
assert trade.pair == pair assert trade.pair == pair
assert trade.open_rate == 0.0001 assert trade.open_rate == 0.0001
@ -1123,11 +1124,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
# Test buy pair not with stakes # Test buy pair not with stakes
with pytest.raises(RPCException, with pytest.raises(RPCException,
match=r'Wrong pair selected. Only pairs with stake-currency.*'): match=r'Wrong pair selected. Only pairs with stake-currency.*'):
rpc._rpc_forcebuy('LTC/ETH', 0.0001) rpc._rpc_force_entry('LTC/ETH', 0.0001)
# Test with defined stake_amount # Test with defined stake_amount
pair = 'LTC/BTC' pair = 'LTC/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05) trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit', stake_amount=0.05)
assert trade.stake_amount == 0.05 assert trade.stake_amount == 0.05
# Test not buying # Test not buying
@ -1137,11 +1138,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
pair = 'TKN/BTC' pair = 'TKN/BTC'
trade = rpc._rpc_forcebuy(pair, None) trade = rpc._rpc_force_entry(pair, None)
assert trade is None assert trade is None
def test_rpcforcebuy_stopped(mocker, default_conf) -> None: def test_rpc_forceentry_stopped(mocker, default_conf) -> None:
default_conf['forcebuy_enable'] = True default_conf['forcebuy_enable'] = True
default_conf['initial_state'] = 'stopped' default_conf['initial_state'] = 'stopped'
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@ -1151,18 +1152,30 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> 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'):
rpc._rpc_forcebuy(pair, None) rpc._rpc_force_entry(pair, None)
def test_rpcforcebuy_disabled(mocker, default_conf) -> None: def test_rpc_forceentry_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) patch_get_signal(freqtradebot)
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'Forceentry not enabled.'):
rpc._rpc_forcebuy(pair, None) rpc._rpc_force_entry(pair, None)
def test_rpc_forceentry_wrong_mode(mocker, default_conf) -> None:
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match="Can't go short on Spot markets."):
rpc._rpc_force_entry(pair, None, order_side=SignalDirection.SHORT)
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")

View File

@ -543,7 +543,7 @@ def test_api_show_config(botclient):
assert 'unfilledtimeout' in response assert 'unfilledtimeout' in response
assert 'version' in response assert 'version' in response
assert 'api_version' in response assert 'api_version' in response
assert 1.1 <= response['api_version'] <= 1.2 assert 2.1 <= response['api_version'] <= 2.2
def test_api_daily(botclient, mocker, ticker, fee, markets): def test_api_daily(botclient, mocker, ticker, fee, markets):
@ -1062,23 +1062,27 @@ def test_api_whitelist(botclient):
# TODO -lev: add test for forcebuy (short) when feature is supported # TODO -lev: add test for forcebuy (short) when feature is supported
def test_api_forcebuy(botclient, mocker, fee): @pytest.mark.parametrize('endpoint', [
'forcebuy',
'forceenter',
])
def test_api_forceentry(botclient, mocker, fee, endpoint):
ftbot, client = botclient ftbot, client = botclient
rc = client_post(client, f"{BASE_URI}/forcebuy", rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}') data='{"pair": "ETH/BTC"}')
assert_response(rc, 502) assert_response(rc, 502)
assert rc.json() == {"error": "Error querying /api/v1/forcebuy: Forcebuy not enabled."} assert rc.json() == {"error": f"Error querying /api/v1/{endpoint}: Forceentry not enabled."}
# enable forcebuy # enable forcebuy
ftbot.config['forcebuy_enable'] = True ftbot.config['forcebuy_enable'] = True
fbuy_mock = MagicMock(return_value=None) fbuy_mock = MagicMock(return_value=None)
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
rc = client_post(client, f"{BASE_URI}/forcebuy", rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}') data='{"pair": "ETH/BTC"}')
assert_response(rc) assert_response(rc)
assert rc.json() == {"status": "Error buying pair ETH/BTC."} assert rc.json() == {"status": "Error entering long trade for pair ETH/BTC."}
# Test creating trade # Test creating trade
fbuy_mock = MagicMock(return_value=Trade( fbuy_mock = MagicMock(return_value=Trade(
@ -1099,9 +1103,9 @@ def test_api_forcebuy(botclient, mocker, fee):
timeframe=5, timeframe=5,
strategy=CURRENT_TEST_STRATEGY strategy=CURRENT_TEST_STRATEGY
)) ))
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
rc = client_post(client, f"{BASE_URI}/forcebuy", rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}') data='{"pair": "ETH/BTC"}')
assert_response(rc) assert_response(rc)
assert rc.json() == { assert rc.json() == {

View File

@ -19,6 +19,7 @@ from freqtrade import __version__
from freqtrade.constants import CANCEL_REASON from freqtrade.constants import CANCEL_REASON
from freqtrade.edge import PairInfo from freqtrade.edge import PairInfo
from freqtrade.enums import RPCMessageType, RunMode, SellType, State from freqtrade.enums import RPCMessageType, RunMode, SellType, State
from freqtrade.enums.signaltype import SignalDirection
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging from freqtrade.loggers import setup_logging
@ -93,8 +94,10 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
assert start_polling.start_polling.call_count == 1 assert start_polling.start_polling.call_count == 1
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], " "['balance'], ['start'], ['stop'], "
"['delete'], ['performance'], ['buys', 'entries'], ['sells'], ['mix_tags'], " "['forcesell', 'forceexit'], ['forcebuy', 'forcelong'], ['forceshort'], "
"['trades'], ['delete'], ['performance'], "
"['buys', 'entries'], ['sells'], ['mix_tags'], "
"['stats'], ['daily'], ['weekly'], ['monthly'], " "['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['unlock', 'delete_locks'], " "['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
@ -940,7 +943,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
# /forcesell 1 # /forcesell 1
context = MagicMock() context = MagicMock()
context.args = ["1"] context.args = ["1"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 4 assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-2][0][0] last_msg = msg_mock.call_args_list[-2][0][0]
@ -1007,7 +1010,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
# /forcesell 1 # /forcesell 1
context = MagicMock() context = MagicMock()
context.args = ["1"] context.args = ["1"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 4 assert msg_mock.call_count == 4
@ -1065,7 +1068,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
# /forcesell all # /forcesell all
context = MagicMock() context = MagicMock()
context.args = ["all"] context.args = ["all"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
# Called for each trade 2 times # Called for each trade 2 times
assert msg_mock.call_count == 8 assert msg_mock.call_count == 8
@ -1109,7 +1112,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
# /forcesell 1 # /forcesell 1
context = MagicMock() context = MagicMock()
context.args = ["1"] context.args = ["1"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0] assert 'not running' in msg_mock.call_args_list[0][0][0]
@ -1118,7 +1121,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
context = MagicMock() context = MagicMock()
context.args = [] context.args = []
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0] assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0]
@ -1128,36 +1131,37 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
# /forcesell 123456 # /forcesell 123456
context = MagicMock() context = MagicMock()
context.args = ["123456"] context.args = ["123456"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'invalid argument' in msg_mock.call_args_list[0][0][0] assert 'invalid argument' in msg_mock.call_args_list[0][0][0]
def test_forcebuy_handle(default_conf, update, mocker) -> None: def test_forceenter_handle(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)
fbuy_mock = MagicMock(return_value=None) fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
# /forcebuy ETH/BTC # /forcelong ETH/BTC
context = MagicMock() context = MagicMock()
context.args = ["ETH/BTC"] context.args = ["ETH/BTC"]
telegram._forcebuy(update=update, context=context) telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 1 assert fbuy_mock.call_count == 1
assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC' assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC'
assert fbuy_mock.call_args_list[0][0][1] is None assert fbuy_mock.call_args_list[0][0][1] is None
assert fbuy_mock.call_args_list[0][1]['order_side'] == SignalDirection.LONG
# Reset and retry with specified price # Reset and retry with specified price
fbuy_mock = MagicMock(return_value=None) fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
# /forcebuy ETH/BTC 0.055 # /forcelong ETH/BTC 0.055
context = MagicMock() context = MagicMock()
context.args = ["ETH/BTC", "0.055"] context.args = ["ETH/BTC", "0.055"]
telegram._forcebuy(update=update, context=context) telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 1 assert fbuy_mock.call_count == 1
assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC' assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC'
@ -1165,24 +1169,24 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None:
assert fbuy_mock.call_args_list[0][0][1] == 0.055 assert fbuy_mock.call_args_list[0][0][1] == 0.055
def test_forcebuy_handle_exception(default_conf, update, mocker) -> None: def test_forceenter_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) patch_get_signal(freqtradebot)
update.message.text = '/forcebuy ETH/Nonepair' update.message.text = '/forcebuy ETH/Nonepair'
telegram._forcebuy(update=update, context=MagicMock()) telegram._forceenter(update=update, context=MagicMock(), order_side=SignalDirection.LONG)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert msg_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.' assert msg_mock.call_args_list[0][0][0] == 'Forceentry not enabled.'
def test_forcebuy_no_pair(default_conf, update, mocker) -> None: def test_forceenter_no_pair(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)
fbuy_mock = MagicMock(return_value=None) fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
@ -1190,7 +1194,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
context = MagicMock() context = MagicMock()
context.args = [] context.args = []
telegram._forcebuy(update=update, context=context) telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 0 assert fbuy_mock.call_count == 0
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
@ -1200,8 +1204,8 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4 assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4
update = MagicMock() update = MagicMock()
update.callback_query = MagicMock() update.callback_query = MagicMock()
update.callback_query.data = 'XRP/USDT' update.callback_query.data = 'XRP/USDT_||_long'
telegram._forcebuy_inline(update, None) telegram._forceenter_inline(update, None)
assert fbuy_mock.call_count == 1 assert fbuy_mock.call_count == 1

View File

@ -179,7 +179,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
assert len(trades) == 4 assert len(trades) == 4
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1 assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1
rpc._rpc_forcebuy('TKN/BTC', None) rpc._rpc_force_entry('TKN/BTC', None)
trades = Trade.query.all() trades = Trade.query.all()
assert len(trades) == 5 assert len(trades) == 5