Merge branch 'develop' into fut/stop_price_type

This commit is contained in:
Matthias
2023-02-09 20:02:59 +01:00
25 changed files with 263 additions and 131 deletions

View File

@@ -32,6 +32,7 @@ class Binance(Exchange):
_ft_has_futures: Dict = {
"stoploss_order_types": {"limit": "stop", "market": "stop_market"},
"tickers_have_price": False,
"floor_leverage": True,
"stop_price_type_field": "workingType",
"stop_price_type_value_mapping": {
PriceType.LAST: "CONTRACT_PRICE",
@@ -88,33 +89,6 @@ class Binance(Exchange):
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def _set_leverage(
self,
leverage: float,
pair: Optional[str] = None,
trading_mode: Optional[TradingMode] = None,
accept_fail: bool = False,
):
"""
Set's the leverage before making a trade, in order to not
have the same leverage on every trade
"""
trading_mode = trading_mode or self.trading_mode
if self._config['dry_run'] or trading_mode != TradingMode.FUTURES:
return
try:
self._api.set_leverage(symbol=pair, leverage=round(leverage))
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
async def _async_get_historic_ohlcv(self, pair: str, timeframe: str,
since_ms: int, candle_type: CandleType,
is_new_pair: bool = False, raise_: bool = False,

View File

@@ -7,6 +7,7 @@ import inspect
import logging
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from math import floor
from threading import Lock
from typing import Any, Coroutine, Dict, List, Literal, Optional, Tuple, Union
@@ -2514,7 +2515,9 @@ class Exchange:
if self._config['dry_run'] or not self.exchange_has("setLeverage"):
# Some exchanges only support one margin_mode type
return
if self._ft_has.get('floor_leverage', False) is True:
# Rounding for binance ...
leverage = floor(leverage)
try:
res = self._api.set_leverage(symbol=pair, leverage=leverage)
self._log_exchange_response('set_leverage', res)

View File

@@ -125,13 +125,15 @@ class Okx(Exchange):
if self.trading_mode != TradingMode.SPOT and self.margin_mode is not None:
try:
# TODO-lev: Test me properly (check mgnMode passed)
self._api.set_leverage(
res = self._api.set_leverage(
leverage=leverage,
symbol=pair,
params={
"mgnMode": self.margin_mode.value,
"posSide": self._get_posSide(side, False),
})
self._log_exchange_response('set_leverage', res)
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:

View File

@@ -363,7 +363,7 @@ class FreqtradeBot(LoggingMixin):
"Order is older than 5 days. Assuming order was fully cancelled.")
fo = order.to_ccxt_object()
fo['status'] = 'canceled'
self.handle_timedout_order(fo, order.trade)
self.handle_cancel_order(fo, order.trade, constants.CANCEL_REASON['TIMEOUT'])
except ExchangeError as e:
@@ -1170,15 +1170,13 @@ class FreqtradeBot(LoggingMixin):
# If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange
if not stoploss_order:
stoploss = (
self.edge.stoploss(pair=trade.pair)
if self.edge else
trade.stop_loss_pct / trade.leverage
)
if trade.is_short:
stop_price = trade.open_rate * (1 - stoploss)
else:
stop_price = trade.open_rate * (1 + stoploss)
stop_price = trade.stoploss_or_liquidation
if self.edge:
stoploss = self.edge.stoploss(pair=trade.pair)
stop_price = (
trade.open_rate * (1 - stoploss) if trade.is_short
else trade.open_rate * (1 + stoploss)
)
if self.create_stoploss_order(trade=trade, stop_price=stop_price):
# The above will return False if the placement failed and the trade was force-sold.
@@ -1263,11 +1261,11 @@ class FreqtradeBot(LoggingMixin):
if not_closed:
if fully_cancelled or (order_obj and self.strategy.ft_check_timed_out(
trade, order_obj, datetime.now(timezone.utc))):
self.handle_timedout_order(order, trade)
self.handle_cancel_order(order, trade, constants.CANCEL_REASON['TIMEOUT'])
else:
self.replace_order(order, order_obj, trade)
def handle_timedout_order(self, order: Dict, trade: Trade) -> None:
def handle_cancel_order(self, order: Dict, trade: Trade, reason: str) -> None:
"""
Check if current analyzed order timed out and cancel if necessary.
:param order: Order dict grabbed with exchange.fetch_order()
@@ -1275,10 +1273,10 @@ class FreqtradeBot(LoggingMixin):
:return: None
"""
if order['side'] == trade.entry_side:
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
self.handle_cancel_enter(trade, order, reason)
else:
canceled = self.handle_cancel_exit(
trade, order, constants.CANCEL_REASON['TIMEOUT'])
trade, order, reason)
canceled_count = trade.get_exit_order_count()
max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:

View File

@@ -21,9 +21,9 @@ class PairLock(_DECL_BASE):
side = Column(String(25), nullable=False, default="*")
reason = Column(String(255), nullable=True)
# Time the pair was locked (start time)
lock_time = Column(DateTime, nullable=False)
lock_time = Column(DateTime(), nullable=False)
# Time until the pair is locked (end time)
lock_end_time = Column(DateTime, nullable=False, index=True)
lock_end_time = Column(DateTime(), nullable=False, index=True)
active = Column(Boolean, nullable=False, default=True, index=True)

View File

@@ -46,31 +46,31 @@ class Order(_DECL_BASE):
trade = relationship("Trade", back_populates="orders")
# order_side can only be 'buy', 'sell' or 'stoploss'
ft_order_side: str = Column(String(25), nullable=False)
ft_pair: str = Column(String(25), nullable=False)
ft_order_side = Column(String(25), nullable=False)
ft_pair = Column(String(25), nullable=False)
ft_is_open = Column(Boolean, nullable=False, default=True, index=True)
ft_amount = Column(Float, nullable=False)
ft_price = Column(Float, nullable=False)
ft_amount = Column(Float(), nullable=False)
ft_price = Column(Float(), nullable=False)
order_id: str = Column(String(255), nullable=False, index=True)
order_id = Column(String(255), nullable=False, index=True)
status = Column(String(255), nullable=True)
symbol = Column(String(25), nullable=True)
order_type: str = Column(String(50), nullable=True)
order_type = Column(String(50), nullable=True)
side = Column(String(25), nullable=True)
price = Column(Float, nullable=True)
average = Column(Float, nullable=True)
amount = Column(Float, nullable=True)
filled = Column(Float, nullable=True)
remaining = Column(Float, nullable=True)
cost = Column(Float, nullable=True)
stop_price = Column(Float, nullable=True)
order_date = Column(DateTime, nullable=True, default=datetime.utcnow)
order_filled_date = Column(DateTime, nullable=True)
order_update_date = Column(DateTime, nullable=True)
price = Column(Float(), nullable=True)
average = Column(Float(), nullable=True)
amount = Column(Float(), nullable=True)
filled = Column(Float(), nullable=True)
remaining = Column(Float(), nullable=True)
cost = Column(Float(), nullable=True)
stop_price = Column(Float(), nullable=True)
order_date = Column(DateTime(), nullable=True, default=datetime.utcnow)
order_filled_date = Column(DateTime(), nullable=True)
order_update_date = Column(DateTime(), nullable=True)
funding_fee = Column(Float, nullable=True)
funding_fee = Column(Float(), nullable=True)
ft_fee_base = Column(Float, nullable=True)
ft_fee_base = Column(Float(), nullable=True)
@property
def order_date_utc(self) -> datetime:
@@ -1177,44 +1177,44 @@ class Trade(_DECL_BASE, LocalTrade):
base_currency = Column(String(25), nullable=True)
stake_currency = Column(String(25), nullable=True)
is_open = Column(Boolean, nullable=False, default=True, index=True)
fee_open = Column(Float, nullable=False, default=0.0)
fee_open_cost = Column(Float, nullable=True)
fee_open = Column(Float(), nullable=False, default=0.0)
fee_open_cost = Column(Float(), nullable=True)
fee_open_currency = Column(String(25), nullable=True)
fee_close = Column(Float, nullable=False, default=0.0)
fee_close_cost = Column(Float, nullable=True)
fee_close = Column(Float(), nullable=False, default=0.0)
fee_close_cost = Column(Float(), nullable=True)
fee_close_currency = Column(String(25), nullable=True)
open_rate: float = Column(Float)
open_rate_requested = Column(Float)
open_rate: float = Column(Float())
open_rate_requested = Column(Float())
# open_trade_value - calculated via _calc_open_trade_value
open_trade_value = Column(Float)
close_rate: Optional[float] = Column(Float)
close_rate_requested = Column(Float)
realized_profit = Column(Float, default=0.0)
close_profit = Column(Float)
close_profit_abs = Column(Float)
stake_amount = Column(Float, nullable=False)
max_stake_amount = Column(Float)
amount = Column(Float)
amount_requested = Column(Float)
open_date = Column(DateTime, nullable=False, default=datetime.utcnow)
close_date = Column(DateTime)
open_trade_value = Column(Float())
close_rate: Optional[float] = Column(Float())
close_rate_requested = Column(Float())
realized_profit = Column(Float(), default=0.0)
close_profit = Column(Float())
close_profit_abs = Column(Float())
stake_amount = Column(Float(), nullable=False)
max_stake_amount = Column(Float())
amount = Column(Float())
amount_requested = Column(Float())
open_date = Column(DateTime(), nullable=False, default=datetime.utcnow)
close_date = Column(DateTime())
open_order_id = Column(String(255))
# absolute value of the stop loss
stop_loss = Column(Float, nullable=True, default=0.0)
stop_loss = Column(Float(), nullable=True, default=0.0)
# percentage value of the stop loss
stop_loss_pct = Column(Float, nullable=True)
stop_loss_pct = Column(Float(), nullable=True)
# absolute value of the initial stop loss
initial_stop_loss = Column(Float, nullable=True, default=0.0)
initial_stop_loss = Column(Float(), nullable=True, default=0.0)
# percentage value of the initial stop loss
initial_stop_loss_pct = Column(Float, nullable=True)
initial_stop_loss_pct = Column(Float(), nullable=True)
# stoploss order id which is on exchange
stoploss_order_id = Column(String(255), nullable=True, index=True)
# last update time of the stoploss order on exchange
stoploss_last_update = Column(DateTime, nullable=True)
stoploss_last_update = Column(DateTime(), nullable=True)
# absolute value of the highest reached price
max_rate = Column(Float, nullable=True, default=0.0)
max_rate = Column(Float(), nullable=True, default=0.0)
# Lowest price reached
min_rate = Column(Float, nullable=True)
min_rate = Column(Float(), nullable=True)
exit_reason = Column(String(100), nullable=True)
exit_order_status = Column(String(100), nullable=True)
strategy = Column(String(100), nullable=True)
@@ -1222,21 +1222,21 @@ class Trade(_DECL_BASE, LocalTrade):
timeframe = Column(Integer, nullable=True)
trading_mode = Column(Enum(TradingMode), nullable=True)
amount_precision = Column(Float, nullable=True)
price_precision = Column(Float, nullable=True)
amount_precision = Column(Float(), nullable=True)
price_precision = Column(Float(), nullable=True)
precision_mode = Column(Integer, nullable=True)
contract_size = Column(Float, nullable=True)
contract_size = Column(Float(), nullable=True)
# Leverage trading properties
leverage = Column(Float, nullable=True, default=1.0)
leverage = Column(Float(), nullable=True, default=1.0)
is_short = Column(Boolean, nullable=False, default=False)
liquidation_price = Column(Float, nullable=True)
liquidation_price = Column(Float(), nullable=True)
# Margin Trading Properties
interest_rate = Column(Float, nullable=False, default=0.0)
interest_rate = Column(Float(), nullable=False, default=0.0)
# Futures properties
funding_fees = Column(Float, nullable=True, default=None)
funding_fees = Column(Float(), nullable=True, default=None)
def __init__(self, **kwargs):
super().__init__(**kwargs)

View File

@@ -168,6 +168,7 @@ class ShowConfig(BaseModel):
max_open_trades: IntOrInf
minimal_roi: Dict[str, Any]
stoploss: Optional[float]
stoploss_on_exchange: bool
trailing_stop: Optional[bool]
trailing_stop_positive: Optional[float]
trailing_stop_positive_offset: Optional[float]

View File

@@ -41,7 +41,8 @@ logger = logging.getLogger(__name__)
# 2.21: Add new_candle messagetype
# 2.22: Add FreqAI to backtesting
# 2.23: Allow plot config request in webserver mode
API_VERSION = 2.23
# 2.24: Add cancel_open_order endpoint
API_VERSION = 2.24
# Public API, requires no auth.
router_public = APIRouter()
@@ -123,6 +124,12 @@ def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete(tradeid)
@router.delete('/trades/{tradeid}/open-order', response_model=OpenTradeSchema, tags=['trading'])
def cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)):
rpc._rpc_cancel_open_order(tradeid)
return rpc._rpc_trade_status([tradeid])[0]
# TODO: Missing response model
@router.get('/edge', tags=['info'])
def edge(rpc: RPC = Depends(get_rpc)):

View File

@@ -122,6 +122,7 @@ class RPC:
if config['max_open_trades'] != float('inf') else -1),
'minimal_roi': config['minimal_roi'].copy() if 'minimal_roi' in config else {},
'stoploss': config.get('stoploss'),
'stoploss_on_exchange': config.get('stoploss_on_exchange', False),
'trailing_stop': config.get('trailing_stop'),
'trailing_stop_positive': config.get('trailing_stop_positive'),
'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'),
@@ -812,6 +813,29 @@ class RPC:
else:
raise RPCException(f'Failed to enter position for {pair}.')
def _rpc_cancel_open_order(self, trade_id: int):
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
with self._freqtrade._exit_lock:
# Query for trade
trade = Trade.get_trades(
trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True), ]
).first()
if not trade:
logger.warning('cancel_open_order: Invalid trade_id received.')
raise RPCException('Invalid trade_id.')
if not trade.open_order_id:
logger.warning('cancel_open_order: No open order for trade_id.')
raise RPCException('No open order for trade_id.')
try:
order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair)
except ExchangeError as e:
logger.info(f"Cannot query order for {trade} due to {e}.", exc_info=True)
raise RPCException("Order not found.")
self._freqtrade.handle_cancel_order(order, trade, CANCEL_REASON['USER_CANCEL'])
Trade.commit()
def _rpc_delete(self, trade_id: int) -> Dict[str, Union[str, int]]:
"""
Handler for delete <id>.

View File

@@ -174,6 +174,7 @@ class Telegram(RPCHandler):
self._force_enter, order_side=SignalDirection.SHORT)),
CommandHandler('trades', self._trades),
CommandHandler('delete', self._delete_trade),
CommandHandler(['coo', 'cancel_open_order'], self._cancel_open_order),
CommandHandler('performance', self._performance),
CommandHandler(['buys', 'entries'], self._enter_tag_performance),
CommandHandler(['sells', 'exits'], self._exit_reason_performance),
@@ -1144,10 +1145,25 @@ class Telegram(RPCHandler):
raise RPCException("Trade-id not set.")
trade_id = int(context.args[0])
msg = self._rpc._rpc_delete(trade_id)
self._send_msg((
self._send_msg(
f"`{msg['result_msg']}`\n"
'Please make sure to take care of this asset on the exchange manually.'
))
)
@authorized_only
def _cancel_open_order(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /cancel_open_order <id>.
Cancel open order for tradeid
:param bot: telegram bot
:param update: message update
:return: None
"""
if not context.args or len(context.args) == 0:
raise RPCException("Trade-id not set.")
trade_id = int(context.args[0])
self._rpc._rpc_cancel_open_order(trade_id)
self._send_msg('Open order canceled.')
@authorized_only
def _performance(self, update: Update, context: CallbackContext) -> None:
@@ -1456,6 +1472,10 @@ class Telegram(RPCHandler):
"*/fx <trade_id>|all:* `Alias to /forceexit`\n"
f"{force_enter_text if self._config.get('force_entry_enable', False) else ''}"
"*/delete <trade_id>:* `Instantly delete the given trade in the database`\n"
"*/cancel_open_order <trade_id>:* `Cancels open orders for trade. "
"Only valid when the trade has open orders.`\n"
"*/coo <trade_id>|all:* `Alias to /cancel_open_order`\n"
"*/whitelist [sorted] [baseonly]:* `Show current whitelist. Optionally in "
"order and/or only displaying the base currency of each pairing.`\n"
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs "