Merge pull request #5976 from freqtrade/forcebuy

allow force options with ordertype
This commit is contained in:
Matthias 2021-11-27 17:01:18 +01:00 committed by GitHub
commit cb95b362ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 45 additions and 27 deletions

View File

@ -466,8 +466,8 @@ class FreqtradeBot(LoggingMixin):
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
return False return False
def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, *,
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: ordertype: Optional[str] = None, 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
@ -510,10 +510,7 @@ class FreqtradeBot(LoggingMixin):
f"{stake_amount} ...") f"{stake_amount} ...")
amount = stake_amount / enter_limit_requested amount = stake_amount / enter_limit_requested
order_type = self.strategy.order_types['buy'] order_type = ordertype or self.strategy.order_types['buy']
if forcebuy:
# Forcebuy can define a different ordertype
order_type = self.strategy.order_types.get('forcebuy', order_type)
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
@ -868,7 +865,7 @@ class FreqtradeBot(LoggingMixin):
logger.info( logger.info(
f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. ' f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. '
f'Tag: {exit_tag if exit_tag is not None else "None"}') f'Tag: {exit_tag if exit_tag is not None else "None"}')
self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag) self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag=exit_tag)
return True return True
return False return False
@ -1081,7 +1078,10 @@ class FreqtradeBot(LoggingMixin):
trade: Trade, trade: Trade,
limit: float, limit: float,
sell_reason: SellCheckTuple, sell_reason: SellCheckTuple,
exit_tag: Optional[str] = None) -> bool: *,
exit_tag: Optional[str] = None,
ordertype: Optional[str] = None,
) -> bool:
""" """
Executes a trade exit for the given trade and limit Executes a trade exit for the given trade and limit
:param trade: Trade instance :param trade: Trade instance
@ -1119,14 +1119,10 @@ class FreqtradeBot(LoggingMixin):
except InvalidOrderException: except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
order_type = self.strategy.order_types[sell_type] order_type = ordertype or self.strategy.order_types[sell_type]
if sell_reason.sell_type == SellType.EMERGENCY_SELL: if sell_reason.sell_type == SellType.EMERGENCY_SELL:
# Emergency sells (default to market!) # Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market") order_type = self.strategy.order_types.get("emergencysell", "market")
if sell_reason.sell_type == SellType.FORCE_SELL:
# Force sells (default to the sell_type defined in the strategy,
# but we allow this value to be changed)
order_type = self.strategy.order_types.get("forcesell", order_type)
amount = self._safe_exit_amount(trade.pair, trade.amount) amount = self._safe_exit_amount(trade.pair, trade.amount)
time_in_force = self.strategy.order_time_in_force['sell'] time_in_force = self.strategy.order_time_in_force['sell']

View File

@ -1,4 +1,5 @@
from datetime import date, datetime from datetime import date, datetime
from enum import Enum
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel from pydantic import BaseModel
@ -131,13 +132,21 @@ class UnfilledTimeout(BaseModel):
exit_timeout_count: Optional[int] exit_timeout_count: Optional[int]
class OrderTypeValues(Enum):
limit = 'limit'
market = 'market'
class Config:
use_enum_values = True
class OrderTypes(BaseModel): class OrderTypes(BaseModel):
buy: str buy: OrderTypeValues
sell: str sell: OrderTypeValues
emergencysell: Optional[str] emergencysell: Optional[OrderTypeValues]
forcesell: Optional[str] forcesell: Optional[OrderTypeValues]
forcebuy: Optional[str] forcebuy: Optional[OrderTypeValues]
stoploss: str stoploss: OrderTypeValues
stoploss_on_exchange: bool stoploss_on_exchange: bool
stoploss_on_exchange_interval: Optional[int] stoploss_on_exchange_interval: Optional[int]
@ -274,10 +283,12 @@ class Logs(BaseModel):
class ForceBuyPayload(BaseModel): class ForceBuyPayload(BaseModel):
pair: str pair: str
price: Optional[float] price: Optional[float]
ordertype: Optional[OrderTypeValues]
class ForceSellPayload(BaseModel): class ForceSellPayload(BaseModel):
tradeid: str tradeid: str
ordertype: Optional[OrderTypeValues]
class BlacklistPayload(BaseModel): class BlacklistPayload(BaseModel):

View File

@ -29,7 +29,8 @@ logger = logging.getLogger(__name__)
# API version # API version
# Pre-1.1, no version was provided # Pre-1.1, no version was provided
# Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen. # Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen.
API_VERSION = 1.1 # 1.11: forcebuy and forcesell accept ordertype
API_VERSION = 1.11
# Public API, requires no auth. # Public API, requires no auth.
router_public = APIRouter() router_public = APIRouter()
@ -129,7 +130,8 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g
@router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading']) @router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading'])
def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)):
trade = rpc._rpc_forcebuy(payload.pair, payload.price) ordertype = payload.ordertype.value if payload.ordertype else None
trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype)
if trade: if trade:
return ForceBuyResponse.parse_obj(trade.to_json()) return ForceBuyResponse.parse_obj(trade.to_json())
@ -139,7 +141,8 @@ def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)):
@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: ForceSellPayload, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_forcesell(payload.tradeid) ordertype = payload.ordertype.value if payload.ordertype else None
return rpc._rpc_forcesell(payload.tradeid, ordertype)
@router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) @router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])

View File

@ -640,7 +640,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) -> Dict[str, str]: def _rpc_forcesell(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
@ -664,7 +664,11 @@ class RPC:
current_rate = self._freqtrade.exchange.get_rate( current_rate = self._freqtrade.exchange.get_rate(
trade.pair, refresh=False, side="sell") trade.pair, refresh=False, side="sell")
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL) sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
self._freqtrade.execute_trade_exit(trade, current_rate, sell_reason) order_type = ordertype or self._freqtrade.strategy.order_types.get(
"forcesell", self._freqtrade.strategy.order_types["sell"])
self._freqtrade.execute_trade_exit(
trade, current_rate, sell_reason, ordertype=order_type)
# ---- EOF def _exec_forcesell ---- # ---- EOF def _exec_forcesell ----
if self._freqtrade.state != State.RUNNING: if self._freqtrade.state != State.RUNNING:
@ -692,7 +696,8 @@ 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]) -> Optional[Trade]: def _rpc_forcebuy(self, pair: str, price: Optional[float],
order_type: Optional[str] = None) -> Optional[Trade]:
""" """
Handler for forcebuy <asset> <price> Handler for forcebuy <asset> <price>
Buys a pair trade at the given or current price Buys a pair trade at the given or current price
@ -720,7 +725,10 @@ class RPC:
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair) stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
# execute buy # execute buy
if self._freqtrade.execute_entry(pair, stakeamount, price, forcebuy=True): if not order_type:
order_type = self._freqtrade.strategy.order_types.get(
'forcebuy', self._freqtrade.strategy.order_types['buy'])
if self._freqtrade.execute_entry(pair, stakeamount, price, ordertype=order_type):
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

@ -1093,7 +1093,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
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_forcebuy(pair, 0.0001)
pair = 'XRP/BTC' pair = 'XRP/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001) trade = rpc._rpc_forcebuy(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