Merge pull request #5976 from freqtrade/forcebuy
allow force options with ordertype
This commit is contained in:
commit
cb95b362ec
@ -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']
|
||||||
|
@ -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):
|
||||||
|
@ -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'])
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user