Delist FTX, following ccxt's delisting.
This commit is contained in:
parent
663039835d
commit
30b467906c
1
.gitignore
vendored
1
.gitignore
vendored
@ -109,7 +109,6 @@ target/
|
|||||||
!*.gitkeep
|
!*.gitkeep
|
||||||
!config_examples/config_binance.example.json
|
!config_examples/config_binance.example.json
|
||||||
!config_examples/config_bittrex.example.json
|
!config_examples/config_bittrex.example.json
|
||||||
!config_examples/config_ftx.example.json
|
|
||||||
!config_examples/config_full.example.json
|
!config_examples/config_full.example.json
|
||||||
!config_examples/config_kraken.example.json
|
!config_examples/config_kraken.example.json
|
||||||
!config_examples/config_freqai.example.json
|
!config_examples/config_freqai.example.json
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
{
|
|
||||||
"max_open_trades": 3,
|
|
||||||
"stake_currency": "USD",
|
|
||||||
"stake_amount": 50,
|
|
||||||
"tradable_balance_ratio": 0.99,
|
|
||||||
"fiat_display_currency": "USD",
|
|
||||||
"timeframe": "5m",
|
|
||||||
"dry_run": true,
|
|
||||||
"cancel_open_orders_on_exit": false,
|
|
||||||
"unfilledtimeout": {
|
|
||||||
"entry": 10,
|
|
||||||
"exit": 10,
|
|
||||||
"exit_timeout_count": 0,
|
|
||||||
"unit": "minutes"
|
|
||||||
},
|
|
||||||
"entry_pricing": {
|
|
||||||
"price_side": "same",
|
|
||||||
"use_order_book": true,
|
|
||||||
"order_book_top": 1,
|
|
||||||
"price_last_balance": 0.0,
|
|
||||||
"check_depth_of_market": {
|
|
||||||
"enabled": false,
|
|
||||||
"bids_to_ask_delta": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exit_pricing": {
|
|
||||||
"price_side": "same",
|
|
||||||
"use_order_book": true,
|
|
||||||
"order_book_top": 1
|
|
||||||
},
|
|
||||||
"exchange": {
|
|
||||||
"name": "ftx",
|
|
||||||
"key": "your_exchange_key",
|
|
||||||
"secret": "your_exchange_secret",
|
|
||||||
"ccxt_config": {},
|
|
||||||
"ccxt_async_config": {},
|
|
||||||
"pair_whitelist": [
|
|
||||||
"BTC/USD",
|
|
||||||
"ETH/USD",
|
|
||||||
"BNB/USD",
|
|
||||||
"USDT/USD",
|
|
||||||
"LTC/USD",
|
|
||||||
"SRM/USD",
|
|
||||||
"SXP/USD",
|
|
||||||
"XRP/USD",
|
|
||||||
"DOGE/USD",
|
|
||||||
"1INCH/USD",
|
|
||||||
"CHZ/USD",
|
|
||||||
"MATIC/USD",
|
|
||||||
"LINK/USD",
|
|
||||||
"OXY/USD",
|
|
||||||
"SUSHI/USD"
|
|
||||||
],
|
|
||||||
"pair_blacklist": [
|
|
||||||
"FTT/USD"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"pairlists": [
|
|
||||||
{"method": "StaticPairList"}
|
|
||||||
],
|
|
||||||
"edge": {
|
|
||||||
"enabled": false,
|
|
||||||
"process_throttle_secs": 3600,
|
|
||||||
"calculate_since_number_of_days": 7,
|
|
||||||
"allowed_risk": 0.01,
|
|
||||||
"stoploss_range_min": -0.01,
|
|
||||||
"stoploss_range_max": -0.1,
|
|
||||||
"stoploss_range_step": -0.01,
|
|
||||||
"minimum_winrate": 0.60,
|
|
||||||
"minimum_expectancy": 0.20,
|
|
||||||
"min_trade_number": 10,
|
|
||||||
"max_trade_duration_minute": 1440,
|
|
||||||
"remove_pumps": false
|
|
||||||
},
|
|
||||||
"telegram": {
|
|
||||||
"enabled": false,
|
|
||||||
"token": "your_telegram_token",
|
|
||||||
"chat_id": "your_telegram_chat_id"
|
|
||||||
},
|
|
||||||
"api_server": {
|
|
||||||
"enabled": false,
|
|
||||||
"listen_ip_address": "127.0.0.1",
|
|
||||||
"listen_port": 8080,
|
|
||||||
"verbosity": "error",
|
|
||||||
"jwt_secret_key": "somethingrandom",
|
|
||||||
"CORS_origins": [],
|
|
||||||
"username": "freqtrader",
|
|
||||||
"password": "SuperSecurePassword"
|
|
||||||
},
|
|
||||||
"bot_name": "freqtrade",
|
|
||||||
"initial_state": "running",
|
|
||||||
"force_entry_enable": false,
|
|
||||||
"internals": {
|
|
||||||
"process_throttle_secs": 5
|
|
||||||
}
|
|
||||||
}
|
|
@ -553,7 +553,7 @@ The possible values are: `GTC` (default), `FOK` or `IOC`.
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
This is ongoing work. For now, it is supported only for binance, gate, ftx and kucoin.
|
This is ongoing work. For now, it is supported only for binance, gate and kucoin.
|
||||||
Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange.
|
Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange.
|
||||||
|
|
||||||
### What values can be used for fiat_display_currency?
|
### What values can be used for fiat_display_currency?
|
||||||
|
@ -173,30 +173,6 @@ res = [p for p, x in lm.items() if 'US' in x['info']['prohibitedIn']]
|
|||||||
print(res)
|
print(res)
|
||||||
```
|
```
|
||||||
|
|
||||||
## FTX
|
|
||||||
|
|
||||||
!!! Warning
|
|
||||||
Due to the current situation, we can no longer recommend FTX.
|
|
||||||
Please make sure to investigate the current situation before depositing any funds to FTX.
|
|
||||||
|
|
||||||
!!! Tip "Stoploss on Exchange"
|
|
||||||
FTX supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
|
||||||
You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used.
|
|
||||||
|
|
||||||
### Using subaccounts
|
|
||||||
|
|
||||||
To use subaccounts with FTX, you need to edit the configuration and add the following:
|
|
||||||
|
|
||||||
``` json
|
|
||||||
"exchange": {
|
|
||||||
"ccxt_config": {
|
|
||||||
"headers": {
|
|
||||||
"FTX-SUBACCOUNT": "name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Kucoin
|
## Kucoin
|
||||||
|
|
||||||
Kucoin requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
Kucoin requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
||||||
|
@ -268,7 +268,7 @@ This option is disabled by default, and will only apply if set to > 0.
|
|||||||
The `max_value` setting removes pairs where the minimum value change is above a specified value.
|
The `max_value` setting removes pairs where the minimum value change is above a specified value.
|
||||||
This is useful when an exchange has unbalanced limits. For example, if step-size = 1 (so you can only buy 1, or 2, or 3, but not 1.1 Coins) - and the price is pretty high (like 20\$) as the coin has risen sharply since the last limit adaption.
|
This is useful when an exchange has unbalanced limits. For example, if step-size = 1 (so you can only buy 1, or 2, or 3, but not 1.1 Coins) - and the price is pretty high (like 20\$) as the coin has risen sharply since the last limit adaption.
|
||||||
As a result of the above, you can only buy for 20\$, or 40\$ - but not for 25\$.
|
As a result of the above, you can only buy for 20\$, or 40\$ - but not for 25\$.
|
||||||
On exchanges that deduct fees from the receiving currency (e.g. binance, FTX) - this can result in high value coins / amounts that are unsellable as the amount is slightly below the limit.
|
On exchanges that deduct fees from the receiving currency (e.g. binance) - this can result in high value coins / amounts that are unsellable as the amount is slightly below the limit.
|
||||||
|
|
||||||
The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio.
|
The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio.
|
||||||
This option is disabled by default, and will only apply if set to > 0.
|
This option is disabled by default, and will only apply if set to > 0.
|
||||||
|
@ -24,7 +24,7 @@ These modes can be configured with these values:
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now.
|
Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now.
|
||||||
<ins>Do not set too low/tight stoploss value if using stop loss on exchange!</ins>
|
<ins>Do not set too low/tight stoploss value if using stop loss on exchange!</ins>
|
||||||
If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work.
|
If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work.
|
||||||
|
|
||||||
|
@ -723,7 +723,7 @@ if self.dp.runmode.value in ('live', 'dry_run'):
|
|||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
Although the ticker data structure is a part of the ccxt Unified Interface, the values returned by this method can
|
Although the ticker data structure is a part of the ccxt Unified Interface, the values returned by this method can
|
||||||
vary for different exchanges. For instance, many exchanges do not return `vwap` values, the FTX exchange
|
vary for different exchanges. For instance, many exchanges do not return `vwap` values, some exchanges
|
||||||
does not always fills in the `last` field (so it can be None), etc. So you need to carefully verify the ticker
|
does not always fills in the `last` field (so it can be None), etc. So you need to carefully verify the ticker
|
||||||
data returned from the exchange and add appropriate error handling / defaults.
|
data returned from the exchange and add appropriate error handling / defaults.
|
||||||
|
|
||||||
|
@ -263,7 +263,6 @@ equos True missing opt: fetchTicker, fetchTickers
|
|||||||
eterbase True
|
eterbase True
|
||||||
fcoin True missing opt: fetchMyTrades, fetchTickers
|
fcoin True missing opt: fetchMyTrades, fetchTickers
|
||||||
fcoinjp True missing opt: fetchMyTrades, fetchTickers
|
fcoinjp True missing opt: fetchMyTrades, fetchTickers
|
||||||
ftx True
|
|
||||||
gateio True
|
gateio True
|
||||||
gemini True
|
gemini True
|
||||||
gopax True
|
gopax True
|
||||||
@ -369,7 +368,6 @@ fcoin True missing opt: fetchMyTrades, fetchTickers
|
|||||||
fcoinjp True missing opt: fetchMyTrades, fetchTickers
|
fcoinjp True missing opt: fetchMyTrades, fetchTickers
|
||||||
flowbtc False missing: fetchOrder, fetchOHLCV
|
flowbtc False missing: fetchOrder, fetchOHLCV
|
||||||
foxbit False missing: fetchOrder, fetchOHLCV
|
foxbit False missing: fetchOrder, fetchOHLCV
|
||||||
ftx True
|
|
||||||
gateio True
|
gateio True
|
||||||
gemini True
|
gemini True
|
||||||
gopax True
|
gopax True
|
||||||
|
@ -108,7 +108,6 @@ def ask_user_config() -> Dict[str, Any]:
|
|||||||
"binance",
|
"binance",
|
||||||
"binanceus",
|
"binanceus",
|
||||||
"bittrex",
|
"bittrex",
|
||||||
"ftx",
|
|
||||||
"gateio",
|
"gateio",
|
||||||
"huobi",
|
"huobi",
|
||||||
"kraken",
|
"kraken",
|
||||||
|
@ -18,7 +18,6 @@ from freqtrade.exchange.exchange_utils import (amount_to_contract_precision, amo
|
|||||||
timeframe_to_next_date, timeframe_to_prev_date,
|
timeframe_to_next_date, timeframe_to_prev_date,
|
||||||
timeframe_to_seconds, validate_exchange,
|
timeframe_to_seconds, validate_exchange,
|
||||||
validate_exchanges)
|
validate_exchanges)
|
||||||
from freqtrade.exchange.ftx import Ftx
|
|
||||||
from freqtrade.exchange.gateio import Gateio
|
from freqtrade.exchange.gateio import Gateio
|
||||||
from freqtrade.exchange.hitbtc import Hitbtc
|
from freqtrade.exchange.hitbtc import Hitbtc
|
||||||
from freqtrade.exchange.huobi import Huobi
|
from freqtrade.exchange.huobi import Huobi
|
||||||
|
@ -52,7 +52,6 @@ MAP_EXCHANGE_CHILDCLASS = {
|
|||||||
SUPPORTED_EXCHANGES = [
|
SUPPORTED_EXCHANGES = [
|
||||||
'binance',
|
'binance',
|
||||||
'bittrex',
|
'bittrex',
|
||||||
'ftx',
|
|
||||||
'gateio',
|
'gateio',
|
||||||
'huobi',
|
'huobi',
|
||||||
'kraken',
|
'kraken',
|
||||||
|
@ -1,178 +0,0 @@
|
|||||||
""" FTX exchange subclass """
|
|
||||||
import logging
|
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
|
||||||
|
|
||||||
import ccxt
|
|
||||||
|
|
||||||
from freqtrade.constants import BuySell
|
|
||||||
from freqtrade.enums import MarginMode, TradingMode
|
|
||||||
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
|
|
||||||
OperationalException, TemporaryError)
|
|
||||||
from freqtrade.exchange import Exchange
|
|
||||||
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT, retrier
|
|
||||||
from freqtrade.misc import safe_value_fallback2
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Ftx(Exchange):
|
|
||||||
|
|
||||||
_ft_has: Dict = {
|
|
||||||
"order_time_in_force": ['GTC', 'IOC', 'PO'],
|
|
||||||
"stoploss_on_exchange": True,
|
|
||||||
"ohlcv_candle_limit": 1500,
|
|
||||||
"ohlcv_require_since": True,
|
|
||||||
"ohlcv_volume_currency": "quote",
|
|
||||||
"mark_ohlcv_price": "index",
|
|
||||||
"mark_ohlcv_timeframe": "1h",
|
|
||||||
}
|
|
||||||
|
|
||||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
|
||||||
# TradingMode.SPOT always supported and not required in this list
|
|
||||||
# (TradingMode.MARGIN, MarginMode.CROSS),
|
|
||||||
# (TradingMode.FUTURES, MarginMode.CROSS)
|
|
||||||
]
|
|
||||||
|
|
||||||
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
|
|
||||||
"""
|
|
||||||
Verify stop_loss against stoploss-order value (limit or price)
|
|
||||||
Returns True if adjustment is necessary.
|
|
||||||
"""
|
|
||||||
return order['type'] == 'stop' and (
|
|
||||||
side == "sell" and stop_loss > float(order['price']) or
|
|
||||||
side == "buy" and stop_loss < float(order['price'])
|
|
||||||
)
|
|
||||||
|
|
||||||
@retrier(retries=0)
|
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float,
|
|
||||||
order_types: Dict, side: BuySell, leverage: float) -> Dict:
|
|
||||||
"""
|
|
||||||
Creates a stoploss order.
|
|
||||||
depending on order_types.stoploss configuration, uses 'market' or limit order.
|
|
||||||
|
|
||||||
Limit orders are defined by having orderPrice set, otherwise a market order is used.
|
|
||||||
"""
|
|
||||||
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
|
|
||||||
if side == "sell":
|
|
||||||
limit_rate = stop_price * limit_price_pct
|
|
||||||
else:
|
|
||||||
limit_rate = stop_price * (2 - limit_price_pct)
|
|
||||||
|
|
||||||
ordertype = "stop"
|
|
||||||
|
|
||||||
stop_price = self.price_to_precision(pair, stop_price)
|
|
||||||
|
|
||||||
if self._config['dry_run']:
|
|
||||||
dry_order = self.create_dry_run_order(
|
|
||||||
pair, ordertype, side, amount, stop_price, leverage, stop_loss=True)
|
|
||||||
return dry_order
|
|
||||||
|
|
||||||
try:
|
|
||||||
params = self._params.copy()
|
|
||||||
if order_types.get('stoploss', 'market') == 'limit':
|
|
||||||
# set orderPrice to place limit order, otherwise it's a market order
|
|
||||||
params['orderPrice'] = limit_rate
|
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
|
||||||
params.update({'reduceOnly': True})
|
|
||||||
|
|
||||||
params['stopPrice'] = stop_price
|
|
||||||
amount = self.amount_to_precision(pair, amount)
|
|
||||||
|
|
||||||
self._lev_prep(pair, leverage, side)
|
|
||||||
order = self._api.create_order(symbol=pair, type=ordertype, side=side,
|
|
||||||
amount=amount, params=params)
|
|
||||||
self._log_exchange_response('create_stoploss_order', order)
|
|
||||||
logger.info('stoploss order added for %s. '
|
|
||||||
'stop price: %s.', pair, stop_price)
|
|
||||||
return order
|
|
||||||
except ccxt.InsufficientFunds as e:
|
|
||||||
raise InsufficientFundsError(
|
|
||||||
f'Insufficient funds to create {ordertype} {side} order on market {pair}. '
|
|
||||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
|
||||||
f'Message: {e}') from e
|
|
||||||
except ccxt.InvalidOrder as e:
|
|
||||||
raise InvalidOrderException(
|
|
||||||
f'Could not create {ordertype} {side} order on market {pair}. '
|
|
||||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
|
||||||
f'Message: {e}') from e
|
|
||||||
except ccxt.DDoSProtection as e:
|
|
||||||
raise DDosProtection(e) from e
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
|
||||||
raise TemporaryError(
|
|
||||||
f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e
|
|
||||||
except ccxt.BaseError as e:
|
|
||||||
raise OperationalException(e) from e
|
|
||||||
|
|
||||||
@retrier(retries=API_FETCH_ORDER_RETRY_COUNT)
|
|
||||||
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
|
|
||||||
if self._config['dry_run']:
|
|
||||||
return self.fetch_dry_run_order(order_id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
orders = self._api.fetch_orders(pair, None, params={'type': 'stop'})
|
|
||||||
|
|
||||||
order = [order for order in orders if order['id'] == order_id]
|
|
||||||
self._log_exchange_response('fetch_stoploss_order', order)
|
|
||||||
if len(order) == 1:
|
|
||||||
if order[0].get('status') == 'closed':
|
|
||||||
# Trigger order was triggered ...
|
|
||||||
real_order_id: Optional[str] = order[0].get('info', {}).get('orderId')
|
|
||||||
# OrderId may be None for stoploss-market orders
|
|
||||||
# So we need to get it through the endpoint
|
|
||||||
# /conditional_orders/{conditional_order_id}/triggers
|
|
||||||
if not real_order_id:
|
|
||||||
res = self._api.privateGetConditionalOrdersConditionalOrderIdTriggers(
|
|
||||||
params={'conditional_order_id': order_id})
|
|
||||||
self._log_exchange_response('fetch_stoploss_order2', res)
|
|
||||||
real_order_id = res['result'][0]['orderId'] if res.get(
|
|
||||||
'result', []) else None
|
|
||||||
|
|
||||||
if real_order_id:
|
|
||||||
order1 = self._api.fetch_order(real_order_id, pair)
|
|
||||||
self._log_exchange_response('fetch_stoploss_order1', order1)
|
|
||||||
# Fake type to stop - as this was really a stop order.
|
|
||||||
order1['id_stop'] = order1['id']
|
|
||||||
order1['id'] = order_id
|
|
||||||
order1['type'] = 'stop'
|
|
||||||
order1['status_stop'] = 'triggered'
|
|
||||||
return order1
|
|
||||||
|
|
||||||
return order[0]
|
|
||||||
else:
|
|
||||||
raise InvalidOrderException(f"Could not get stoploss order for id {order_id}")
|
|
||||||
|
|
||||||
except ccxt.InvalidOrder as e:
|
|
||||||
raise InvalidOrderException(
|
|
||||||
f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
|
|
||||||
except ccxt.DDoSProtection as e:
|
|
||||||
raise DDosProtection(e) from e
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
|
||||||
raise TemporaryError(
|
|
||||||
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
|
|
||||||
except ccxt.BaseError as e:
|
|
||||||
raise OperationalException(e) from e
|
|
||||||
|
|
||||||
@retrier
|
|
||||||
def cancel_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
|
|
||||||
if self._config['dry_run']:
|
|
||||||
return {}
|
|
||||||
try:
|
|
||||||
order = self._api.cancel_order(order_id, pair, params={'type': 'stop'})
|
|
||||||
self._log_exchange_response('cancel_stoploss_order', order)
|
|
||||||
return order
|
|
||||||
except ccxt.InvalidOrder as e:
|
|
||||||
raise InvalidOrderException(
|
|
||||||
f'Could not cancel order. Message: {e}') from e
|
|
||||||
except ccxt.DDoSProtection as e:
|
|
||||||
raise DDosProtection(e) from e
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
|
||||||
raise TemporaryError(
|
|
||||||
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
|
|
||||||
except ccxt.BaseError as e:
|
|
||||||
raise OperationalException(e) from e
|
|
||||||
|
|
||||||
def get_order_id_conditional(self, order: Dict[str, Any]) -> str:
|
|
||||||
if order['type'] == 'stop':
|
|
||||||
return safe_value_fallback2(order, order, 'id_stop', 'id')
|
|
||||||
return order['id']
|
|
@ -35,9 +35,5 @@ def interest(
|
|||||||
elif exchange_name == "kraken":
|
elif exchange_name == "kraken":
|
||||||
# Rounded based on https://kraken-fees-calculator.github.io/
|
# Rounded based on https://kraken-fees-calculator.github.io/
|
||||||
return borrowed * rate * (one + FtPrecise(ceil(hours / four)))
|
return borrowed * rate * (one + FtPrecise(ceil(hours / four)))
|
||||||
elif exchange_name == "ftx":
|
|
||||||
# As Explained under #Interest rates section in
|
|
||||||
# https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer
|
|
||||||
return borrowed * rate * FtPrecise(ceil(hours)) / twenty_four
|
|
||||||
else:
|
else:
|
||||||
raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade")
|
raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade")
|
||||||
|
@ -30,7 +30,7 @@ def test_validate_is_int():
|
|||||||
assert not validate_is_int('-ee')
|
assert not validate_is_int('-ee')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx'])
|
@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken'])
|
||||||
def test_start_new_config(mocker, caplog, exchange):
|
def test_start_new_config(mocker, caplog, exchange):
|
||||||
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
|
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
|
||||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||||
|
@ -1748,28 +1748,7 @@ def limit_buy_order_canceled_empty(request):
|
|||||||
# https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments
|
# https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments
|
||||||
|
|
||||||
exchange_name = request.param
|
exchange_name = request.param
|
||||||
if exchange_name == 'ftx':
|
if exchange_name == 'kraken':
|
||||||
return {
|
|
||||||
'info': {},
|
|
||||||
'id': '1234512345',
|
|
||||||
'clientOrderId': None,
|
|
||||||
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
|
|
||||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
|
||||||
'lastTradeTimestamp': None,
|
|
||||||
'symbol': 'LTC/USDT',
|
|
||||||
'type': 'limit',
|
|
||||||
'side': 'buy',
|
|
||||||
'price': 34.3225,
|
|
||||||
'amount': 0.55,
|
|
||||||
'cost': 0.0,
|
|
||||||
'average': None,
|
|
||||||
'filled': 0.0,
|
|
||||||
'remaining': 0.0,
|
|
||||||
'status': 'closed',
|
|
||||||
'fee': None,
|
|
||||||
'trades': None
|
|
||||||
}
|
|
||||||
elif exchange_name == 'kraken':
|
|
||||||
return {
|
return {
|
||||||
'info': {},
|
'info': {},
|
||||||
'id': 'AZNPFF-4AC4N-7MKTAT',
|
'id': 'AZNPFF-4AC4N-7MKTAT',
|
||||||
|
@ -70,7 +70,7 @@ def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype):
|
|||||||
('BTC_USDT_USDT', 'BTC/USDT:USDT'), # Futures
|
('BTC_USDT_USDT', 'BTC/USDT:USDT'), # Futures
|
||||||
('XRP_USDT_USDT', 'XRP/USDT:USDT'), # futures
|
('XRP_USDT_USDT', 'XRP/USDT:USDT'), # futures
|
||||||
('BTC-PERP', 'BTC-PERP'),
|
('BTC-PERP', 'BTC-PERP'),
|
||||||
('BTC-PERP_USDT', 'BTC-PERP:USDT'), # potential FTX case
|
('BTC-PERP_USDT', 'BTC-PERP:USDT'),
|
||||||
('UNITTEST_USDT', 'UNITTEST/USDT'),
|
('UNITTEST_USDT', 'UNITTEST/USDT'),
|
||||||
])
|
])
|
||||||
def test_rebuild_pair_from_filename(input, expected):
|
def test_rebuild_pair_from_filename(input, expected):
|
||||||
|
@ -45,16 +45,6 @@ EXCHANGES = {
|
|||||||
'leverage_tiers_public': False,
|
'leverage_tiers_public': False,
|
||||||
'leverage_in_spot_market': True,
|
'leverage_in_spot_market': True,
|
||||||
},
|
},
|
||||||
# 'ftx': {
|
|
||||||
# 'pair': 'BTC/USD',
|
|
||||||
# 'stake_currency': 'USD',
|
|
||||||
# 'hasQuoteVolume': True,
|
|
||||||
# 'timeframe': '5m',
|
|
||||||
# 'futures_pair': 'BTC/USD:USD',
|
|
||||||
# 'futures': False,
|
|
||||||
# 'leverage_tiers_public': False, # TODO: Set to True once implemented on CCXT
|
|
||||||
# 'leverage_in_spot_market': True,
|
|
||||||
# },
|
|
||||||
'kucoin': {
|
'kucoin': {
|
||||||
'pair': 'XRP/USDT',
|
'pair': 'XRP/USDT',
|
||||||
'stake_currency': 'USDT',
|
'stake_currency': 'USDT',
|
||||||
|
@ -27,7 +27,7 @@ from tests.conftest import (generate_test_data_raw, get_mock_coro, get_patched_e
|
|||||||
|
|
||||||
|
|
||||||
# Make sure to always keep one exchange here which is NOT subclassed!!
|
# Make sure to always keep one exchange here which is NOT subclassed!!
|
||||||
EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx', 'gateio']
|
EXCHANGES = ['bittrex', 'binance', 'kraken', 'gateio']
|
||||||
|
|
||||||
get_entry_rate_data = [
|
get_entry_rate_data = [
|
||||||
('other', 20, 19, 10, 0.0, 20), # Full ask side
|
('other', 20, 19, 10, 0.0, 20), # Full ask side
|
||||||
@ -3162,19 +3162,16 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
|
|||||||
def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123})
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123})
|
||||||
mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', return_value={'for': 123})
|
|
||||||
mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', return_value={'for': 123})
|
mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', return_value={'for': 123})
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||||
|
|
||||||
res = {'fee': {}, 'status': 'canceled', 'amount': 1234}
|
res = {'fee': {}, 'status': 'canceled', 'amount': 1234}
|
||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value=res)
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value=res)
|
||||||
mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', return_value=res)
|
|
||||||
mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', return_value=res)
|
mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', return_value=res)
|
||||||
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
||||||
assert co == res
|
assert co == res
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value='canceled')
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value='canceled')
|
||||||
mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', return_value='canceled')
|
|
||||||
mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', return_value='canceled')
|
mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', return_value='canceled')
|
||||||
# Fall back to fetch_stoploss_order
|
# Fall back to fetch_stoploss_order
|
||||||
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
||||||
@ -3182,7 +3179,6 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
|||||||
|
|
||||||
exc = InvalidOrderException("")
|
exc = InvalidOrderException("")
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=exc)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=exc)
|
||||||
mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', side_effect=exc)
|
|
||||||
mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', side_effect=exc)
|
mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', side_effect=exc)
|
||||||
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
||||||
assert co['amount'] == 555
|
assert co['amount'] == 555
|
||||||
@ -3191,7 +3187,6 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
|||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
exc = InvalidOrderException("Did not find order")
|
exc = InvalidOrderException("Did not find order")
|
||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=exc)
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=exc)
|
||||||
mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', side_effect=exc)
|
|
||||||
mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', side_effect=exc)
|
mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', side_effect=exc)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||||
exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
|
exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
|
||||||
@ -3253,9 +3248,6 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
|
|||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
|
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
|
||||||
# Don't test FTX here - that needs a separate test
|
|
||||||
if exchange_name == 'ftx':
|
|
||||||
return
|
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
order = MagicMock()
|
order = MagicMock()
|
||||||
order.myid = 123
|
order.myid = 123
|
||||||
@ -3699,16 +3691,6 @@ def test_date_minus_candles():
|
|||||||
# no darkpools
|
# no darkpools
|
||||||
("BTC/EUR.d", 'BTC', 'EUR', "kraken", True, False, False, 'spot',
|
("BTC/EUR.d", 'BTC', 'EUR', "kraken", True, False, False, 'spot',
|
||||||
{"darkpool": True}, False),
|
{"darkpool": True}, False),
|
||||||
("BTC/USD", 'BTC', 'USD', "ftx", True, False, False, 'spot', {}, True),
|
|
||||||
("USD/BTC", 'USD', 'BTC', "ftx", True, False, False, 'spot', {}, True),
|
|
||||||
# Can only trade spot markets
|
|
||||||
("BTC/USD", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False),
|
|
||||||
("BTC/USD", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True),
|
|
||||||
# Can only trade spot markets
|
|
||||||
("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False),
|
|
||||||
("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'margin', {}, False),
|
|
||||||
("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True),
|
|
||||||
|
|
||||||
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'spot', {}, False),
|
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'spot', {}, False),
|
||||||
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'margin', {}, False),
|
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'margin', {}, False),
|
||||||
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'futures', {}, True),
|
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'futures', {}, True),
|
||||||
@ -3841,7 +3823,7 @@ def test_calculate_backoff(retrycount, max_retries, expected):
|
|||||||
assert calculate_backoff(retrycount, max_retries) == expected
|
assert calculate_backoff(retrycount, max_retries) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", ['binance', 'ftx'])
|
@pytest.mark.parametrize("exchange_name", ['binance'])
|
||||||
def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
|
def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.fetch_funding_history = MagicMock(return_value=[
|
api_mock.fetch_funding_history = MagicMock(return_value=[
|
||||||
@ -3909,7 +3891,7 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('exchange', ['binance', 'kraken', 'ftx'])
|
@pytest.mark.parametrize('exchange', ['binance', 'kraken'])
|
||||||
@pytest.mark.parametrize('stake_amount,leverage,min_stake_with_lev', [
|
@pytest.mark.parametrize('stake_amount,leverage,min_stake_with_lev', [
|
||||||
(9.0, 3.0, 3.0),
|
(9.0, 3.0, 3.0),
|
||||||
(20.0, 5.0, 4.0),
|
(20.0, 5.0, 4.0),
|
||||||
@ -3930,8 +3912,6 @@ def test_get_stake_amount_considering_leverage(
|
|||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name,trading_mode", [
|
@pytest.mark.parametrize("exchange_name,trading_mode", [
|
||||||
("binance", TradingMode.FUTURES),
|
("binance", TradingMode.FUTURES),
|
||||||
("ftx", TradingMode.MARGIN),
|
|
||||||
("ftx", TradingMode.FUTURES)
|
|
||||||
])
|
])
|
||||||
def test__set_leverage(mocker, default_conf, exchange_name, trading_mode):
|
def test__set_leverage(mocker, default_conf, exchange_name, trading_mode):
|
||||||
|
|
||||||
@ -3982,9 +3962,6 @@ def test_set_margin_mode(mocker, default_conf, margin_mode):
|
|||||||
("kraken", TradingMode.SPOT, None, False),
|
("kraken", TradingMode.SPOT, None, False),
|
||||||
("kraken", TradingMode.MARGIN, MarginMode.ISOLATED, True),
|
("kraken", TradingMode.MARGIN, MarginMode.ISOLATED, True),
|
||||||
("kraken", TradingMode.FUTURES, MarginMode.ISOLATED, True),
|
("kraken", TradingMode.FUTURES, MarginMode.ISOLATED, True),
|
||||||
("ftx", TradingMode.SPOT, None, False),
|
|
||||||
("ftx", TradingMode.MARGIN, MarginMode.ISOLATED, True),
|
|
||||||
("ftx", TradingMode.FUTURES, MarginMode.ISOLATED, True),
|
|
||||||
("bittrex", TradingMode.SPOT, None, False),
|
("bittrex", TradingMode.SPOT, None, False),
|
||||||
("bittrex", TradingMode.MARGIN, MarginMode.CROSS, True),
|
("bittrex", TradingMode.MARGIN, MarginMode.CROSS, True),
|
||||||
("bittrex", TradingMode.MARGIN, MarginMode.ISOLATED, True),
|
("bittrex", TradingMode.MARGIN, MarginMode.ISOLATED, True),
|
||||||
@ -4005,8 +3982,6 @@ def test_set_margin_mode(mocker, default_conf, margin_mode):
|
|||||||
("binance", TradingMode.FUTURES, MarginMode.CROSS, True),
|
("binance", TradingMode.FUTURES, MarginMode.CROSS, True),
|
||||||
("kraken", TradingMode.MARGIN, MarginMode.CROSS, True),
|
("kraken", TradingMode.MARGIN, MarginMode.CROSS, True),
|
||||||
("kraken", TradingMode.FUTURES, MarginMode.CROSS, True),
|
("kraken", TradingMode.FUTURES, MarginMode.CROSS, True),
|
||||||
("ftx", TradingMode.MARGIN, MarginMode.CROSS, True),
|
|
||||||
("ftx", TradingMode.FUTURES, MarginMode.CROSS, True),
|
|
||||||
("gateio", TradingMode.MARGIN, MarginMode.CROSS, True),
|
("gateio", TradingMode.MARGIN, MarginMode.CROSS, True),
|
||||||
("gateio", TradingMode.FUTURES, MarginMode.CROSS, True),
|
("gateio", TradingMode.FUTURES, MarginMode.CROSS, True),
|
||||||
|
|
||||||
@ -4015,8 +3990,6 @@ def test_set_margin_mode(mocker, default_conf, margin_mode):
|
|||||||
# ("binance", TradingMode.FUTURES, MarginMode.CROSS, False),
|
# ("binance", TradingMode.FUTURES, MarginMode.CROSS, False),
|
||||||
# ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False),
|
# ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False),
|
||||||
# ("kraken", TradingMode.FUTURES, MarginMode.CROSS, False),
|
# ("kraken", TradingMode.FUTURES, MarginMode.CROSS, False),
|
||||||
# ("ftx", TradingMode.MARGIN, MarginMode.CROSS, False),
|
|
||||||
# ("ftx", TradingMode.FUTURES, MarginMode.CROSS, False),
|
|
||||||
# ("gateio", TradingMode.MARGIN, MarginMode.CROSS, False),
|
# ("gateio", TradingMode.MARGIN, MarginMode.CROSS, False),
|
||||||
# ("gateio", TradingMode.FUTURES, MarginMode.CROSS, False),
|
# ("gateio", TradingMode.FUTURES, MarginMode.CROSS, False),
|
||||||
])
|
])
|
||||||
@ -4046,7 +4019,6 @@ def test_validate_trading_mode_and_margin_mode(
|
|||||||
("bibox", "futures", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "swap"}}),
|
("bibox", "futures", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "swap"}}),
|
||||||
("bybit", "spot", {"options": {"defaultType": "spot"}}),
|
("bybit", "spot", {"options": {"defaultType": "spot"}}),
|
||||||
("bybit", "futures", {"options": {"defaultType": "linear"}}),
|
("bybit", "futures", {"options": {"defaultType": "linear"}}),
|
||||||
("ftx", "futures", {"options": {"defaultType": "swap"}}),
|
|
||||||
("gateio", "futures", {"options": {"defaultType": "swap"}}),
|
("gateio", "futures", {"options": {"defaultType": "swap"}}),
|
||||||
("hitbtc", "futures", {"options": {"defaultType": "swap"}}),
|
("hitbtc", "futures", {"options": {"defaultType": "swap"}}),
|
||||||
("kraken", "futures", {"options": {"defaultType": "swap"}}),
|
("kraken", "futures", {"options": {"defaultType": "swap"}}),
|
||||||
@ -4223,11 +4195,6 @@ def test_combine_funding_and_mark(
|
|||||||
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0012443999999999999),
|
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0012443999999999999),
|
||||||
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0045759),
|
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0045759),
|
||||||
# ('kraken', "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0008289),
|
# ('kraken', "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0008289),
|
||||||
('ftx', 0, 2, "2021-09-01 00:10:00", "2021-09-01 00:30:00", 30.0, 0.0),
|
|
||||||
('ftx', 0, 9, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, 0.0010008),
|
|
||||||
('ftx', 0, 13, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0146691),
|
|
||||||
('ftx', 0, 9, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, 0.001668),
|
|
||||||
('ftx', 1, 9, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, 0.0019932),
|
|
||||||
('gateio', 0, 2, "2021-09-01 00:10:00", "2021-09-01 04:00:00", 30.0, 0.0),
|
('gateio', 0, 2, "2021-09-01 00:10:00", "2021-09-01 04:00:00", 30.0, 0.0),
|
||||||
('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999),
|
('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999),
|
||||||
('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999),
|
('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999),
|
||||||
@ -4289,7 +4256,6 @@ def test__fetch_and_calculate_funding_fees(
|
|||||||
d2 = datetime.strptime(f"{d2} +0000", '%Y-%m-%d %H:%M:%S %z')
|
d2 = datetime.strptime(f"{d2} +0000", '%Y-%m-%d %H:%M:%S %z')
|
||||||
funding_rate_history = {
|
funding_rate_history = {
|
||||||
'binance': funding_rate_history_octohourly,
|
'binance': funding_rate_history_octohourly,
|
||||||
'ftx': funding_rate_history_hourly,
|
|
||||||
'gateio': funding_rate_history_octohourly,
|
'gateio': funding_rate_history_octohourly,
|
||||||
}[exchange][rate_start:rate_end]
|
}[exchange][rate_start:rate_end]
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
@ -5056,7 +5022,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
|
|||||||
exchange.get_max_leverage("BTC/USDT", 1000000000.01)
|
exchange.get_max_leverage("BTC/USDT", 1000000000.01)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'ftx', 'gateio', 'okx'])
|
@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gateio', 'okx'])
|
||||||
def test__get_params(mocker, default_conf, exchange_name):
|
def test__get_params(mocker, default_conf, exchange_name):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
|
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
|
||||||
|
@ -1,272 +0,0 @@
|
|||||||
from random import randint
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
import ccxt
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from freqtrade.exceptions import DependencyException, InvalidOrderException
|
|
||||||
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT
|
|
||||||
from tests.conftest import get_patched_exchange
|
|
||||||
|
|
||||||
from .test_exchange import ccxt_exceptionhandlers
|
|
||||||
|
|
||||||
|
|
||||||
STOPLOSS_ORDERTYPE = 'stop'
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('order_price,exchangelimitratio,side', [
|
|
||||||
(217.8, 1.05, "sell"),
|
|
||||||
(222.2, 0.95, "buy"),
|
|
||||||
])
|
|
||||||
def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitratio, side):
|
|
||||||
api_mock = MagicMock()
|
|
||||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
|
||||||
|
|
||||||
api_mock.create_order = MagicMock(return_value={
|
|
||||||
'id': order_id,
|
|
||||||
'info': {
|
|
||||||
'foo': 'bar'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
|
||||||
|
|
||||||
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
|
|
||||||
order = exchange.stoploss(
|
|
||||||
pair='ETH/BTC',
|
|
||||||
amount=1,
|
|
||||||
stop_price=190,
|
|
||||||
side=side,
|
|
||||||
order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio},
|
|
||||||
leverage=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['side'] == side
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
|
||||||
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
|
|
||||||
assert 'stopPrice' in api_mock.create_order.call_args_list[0][1]['params']
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 190
|
|
||||||
|
|
||||||
assert api_mock.create_order.call_count == 1
|
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
|
||||||
|
|
||||||
order = exchange.stoploss(
|
|
||||||
pair='ETH/BTC',
|
|
||||||
amount=1,
|
|
||||||
stop_price=220,
|
|
||||||
order_types={},
|
|
||||||
side=side,
|
|
||||||
leverage=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
assert 'id' in order
|
|
||||||
assert 'info' in order
|
|
||||||
assert order['id'] == order_id
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['side'] == side
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
|
||||||
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
|
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
|
||||||
order = exchange.stoploss(
|
|
||||||
pair='ETH/BTC',
|
|
||||||
amount=1,
|
|
||||||
stop_price=220,
|
|
||||||
order_types={'stoploss': 'limit'}, side=side,
|
|
||||||
leverage=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
assert 'id' in order
|
|
||||||
assert 'info' in order
|
|
||||||
assert order['id'] == order_id
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['side'] == side
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
|
||||||
assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params']
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == order_price
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
|
|
||||||
|
|
||||||
# test exception handling
|
|
||||||
with pytest.raises(DependencyException):
|
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
|
||||||
exchange.stoploss(
|
|
||||||
pair='ETH/BTC',
|
|
||||||
amount=1,
|
|
||||||
stop_price=220,
|
|
||||||
order_types={},
|
|
||||||
side=side,
|
|
||||||
leverage=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
|
||||||
api_mock.create_order = MagicMock(
|
|
||||||
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
|
||||||
exchange.stoploss(
|
|
||||||
pair='ETH/BTC',
|
|
||||||
amount=1,
|
|
||||||
stop_price=220,
|
|
||||||
order_types={},
|
|
||||||
side=side,
|
|
||||||
leverage=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
|
|
||||||
"stoploss", "create_order", retries=1,
|
|
||||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
|
||||||
side=side, leverage=1.0)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('side', [("sell"), ("buy")])
|
|
||||||
def test_stoploss_order_dry_run_ftx(default_conf, mocker, side):
|
|
||||||
api_mock = MagicMock()
|
|
||||||
default_conf['dry_run'] = True
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
|
||||||
|
|
||||||
order = exchange.stoploss(
|
|
||||||
pair='ETH/BTC',
|
|
||||||
amount=1,
|
|
||||||
stop_price=220,
|
|
||||||
order_types={},
|
|
||||||
side=side,
|
|
||||||
leverage=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
assert 'id' in order
|
|
||||||
assert 'info' in order
|
|
||||||
assert 'type' in order
|
|
||||||
|
|
||||||
assert order['type'] == STOPLOSS_ORDERTYPE
|
|
||||||
assert order['price'] == 220
|
|
||||||
assert order['amount'] == 1
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
|
|
||||||
(1501, 1499, 1501, "sell"),
|
|
||||||
(1499, 1501, 1499, "buy")
|
|
||||||
])
|
|
||||||
def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side):
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
|
|
||||||
order = {
|
|
||||||
'type': STOPLOSS_ORDERTYPE,
|
|
||||||
'price': 1500,
|
|
||||||
}
|
|
||||||
assert exchange.stoploss_adjust(sl1, order, side=side)
|
|
||||||
assert not exchange.stoploss_adjust(sl2, order, side=side)
|
|
||||||
# Test with invalid order case ...
|
|
||||||
order['type'] = 'stop_loss_limit'
|
|
||||||
assert not exchange.stoploss_adjust(sl3, order, side=side)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
|
||||||
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_buy_order):
|
|
||||||
default_conf['dry_run'] = True
|
|
||||||
order = MagicMock()
|
|
||||||
order.myid = 123
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
|
|
||||||
exchange._dry_run_open_orders['X'] = order
|
|
||||||
assert exchange.fetch_stoploss_order('X', 'TKN/BTC').myid == 123
|
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
|
|
||||||
exchange.fetch_stoploss_order('Y', 'TKN/BTC')
|
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
|
||||||
api_mock = MagicMock()
|
|
||||||
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': '456'}])
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
|
||||||
assert exchange.fetch_stoploss_order('X', 'TKN/BTC')['status'] == '456'
|
|
||||||
|
|
||||||
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'Y', 'status': '456'}])
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
|
||||||
with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"):
|
|
||||||
exchange.fetch_stoploss_order('X', 'TKN/BTC')['status']
|
|
||||||
|
|
||||||
# stoploss Limit order
|
|
||||||
api_mock.fetch_orders = MagicMock(return_value=[
|
|
||||||
{'id': 'X', 'status': 'closed',
|
|
||||||
'info': {
|
|
||||||
'orderId': 'mocked_limit_sell',
|
|
||||||
}}])
|
|
||||||
api_mock.fetch_order = MagicMock(return_value=limit_sell_order.copy())
|
|
||||||
|
|
||||||
# No orderId field - no call to fetch_order
|
|
||||||
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
|
|
||||||
assert resp
|
|
||||||
assert api_mock.fetch_order.call_count == 1
|
|
||||||
assert resp['id_stop'] == 'mocked_limit_sell'
|
|
||||||
assert resp['id'] == 'X'
|
|
||||||
assert resp['type'] == 'stop'
|
|
||||||
assert resp['status_stop'] == 'triggered'
|
|
||||||
|
|
||||||
# Stoploss market order
|
|
||||||
# Contains no new Order, but "average" instead
|
|
||||||
order = {'id': 'X', 'status': 'closed', 'info': {'orderId': None}, 'average': 0.254}
|
|
||||||
api_mock.fetch_orders = MagicMock(return_value=[order])
|
|
||||||
api_mock.fetch_order.reset_mock()
|
|
||||||
api_mock.privateGetConditionalOrdersConditionalOrderIdTriggers = MagicMock(
|
|
||||||
return_value={'result': [
|
|
||||||
{'orderId': 'mocked_market_sell', 'type': 'market', 'side': 'sell', 'price': 0.254}
|
|
||||||
]})
|
|
||||||
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
|
|
||||||
assert resp
|
|
||||||
# fetch_order not called (no regular order ID)
|
|
||||||
assert api_mock.fetch_order.call_count == 1
|
|
||||||
api_mock.privateGetConditionalOrdersConditionalOrderIdTriggers.call_count == 1
|
|
||||||
expected_resp = limit_sell_order.copy()
|
|
||||||
expected_resp.update({
|
|
||||||
'id_stop': 'X',
|
|
||||||
'id': 'X',
|
|
||||||
'type': 'stop',
|
|
||||||
'status_stop': 'triggered',
|
|
||||||
})
|
|
||||||
assert expected_resp == resp
|
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
|
||||||
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
|
||||||
exchange.fetch_stoploss_order(order_id='_', pair='TKN/BTC')
|
|
||||||
assert api_mock.fetch_orders.call_count == 1
|
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx',
|
|
||||||
'fetch_stoploss_order', 'fetch_orders',
|
|
||||||
retries=API_FETCH_ORDER_RETRY_COUNT + 1,
|
|
||||||
order_id='_', pair='TKN/BTC')
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_order_id(mocker, default_conf):
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
|
|
||||||
order = {
|
|
||||||
'type': STOPLOSS_ORDERTYPE,
|
|
||||||
'price': 1500,
|
|
||||||
'id': '1111',
|
|
||||||
'id_stop': '1234',
|
|
||||||
'info': {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert exchange.get_order_id_conditional(order) == '1234'
|
|
||||||
|
|
||||||
order = {
|
|
||||||
'type': 'limit',
|
|
||||||
'price': 1500,
|
|
||||||
'id': '1111',
|
|
||||||
'id_stop': '1234',
|
|
||||||
'info': {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert exchange.get_order_id_conditional(order) == '1111'
|
|
@ -19,11 +19,6 @@ twentyfive_hours = FtPrecise(25.0)
|
|||||||
('kraken', 0.00025, ten_mins, 0.03),
|
('kraken', 0.00025, ten_mins, 0.03),
|
||||||
('kraken', 0.00025, five_hours, 0.045),
|
('kraken', 0.00025, five_hours, 0.045),
|
||||||
('kraken', 0.00025, twentyfive_hours, 0.12),
|
('kraken', 0.00025, twentyfive_hours, 0.12),
|
||||||
# FTX
|
|
||||||
('ftx', 0.0005, ten_mins, 0.00125),
|
|
||||||
('ftx', 0.00025, ten_mins, 0.000625),
|
|
||||||
('ftx', 0.00025, five_hours, 0.003125),
|
|
||||||
('ftx', 0.00025, twentyfive_hours, 0.015625),
|
|
||||||
])
|
])
|
||||||
def test_interest(exchange, interest_rate, hours, expected):
|
def test_interest(exchange, interest_rate, hours, expected):
|
||||||
borrowed = FtPrecise(60.0)
|
borrowed = FtPrecise(60.0)
|
||||||
|
@ -612,9 +612,9 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
|||||||
"lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}],
|
"lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}],
|
||||||
"BTC", "binance", ['ETH/BTC', 'LTC/BTC', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
|
"BTC", "binance", ['ETH/BTC', 'LTC/BTC', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
|
||||||
# ftx data is already in Quote currency, therefore won't require conversion
|
# ftx data is already in Quote currency, therefore won't require conversion
|
||||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
# ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||||
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
|
# "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
|
||||||
"BTC", "ftx", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']),
|
# "BTC", "ftx", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']),
|
||||||
])
|
])
|
||||||
def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history,
|
def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history,
|
||||||
pairlists, base_currency, exchange, volumefilter_result) -> None:
|
pairlists, base_currency, exchange, volumefilter_result) -> None:
|
||||||
@ -636,8 +636,6 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers,
|
|||||||
ohlcv_history_high_volume['high'] = ohlcv_history_high_volume.loc[:, 'high'] * 0.01
|
ohlcv_history_high_volume['high'] = ohlcv_history_high_volume.loc[:, 'high'] * 0.01
|
||||||
ohlcv_history_high_volume['close'] = ohlcv_history_high_volume.loc[:, 'close'] * 0.01
|
ohlcv_history_high_volume['close'] = ohlcv_history_high_volume.loc[:, 'close'] * 0.01
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.ftx.Ftx.market_is_tradable', return_value=True)
|
|
||||||
|
|
||||||
ohlcv_data = {
|
ohlcv_data = {
|
||||||
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
|
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
|
||||||
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
|
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
|
||||||
|
@ -3036,7 +3036,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_short", [False, True])
|
@pytest.mark.parametrize("is_short", [False, True])
|
||||||
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
|
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'kraken', 'bittrex'],
|
||||||
indirect=['limit_buy_order_canceled_empty'])
|
indirect=['limit_buy_order_canceled_empty'])
|
||||||
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short, fee,
|
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short, fee,
|
||||||
limit_buy_order_canceled_empty) -> None:
|
limit_buy_order_canceled_empty) -> None:
|
||||||
|
Loading…
Reference in New Issue
Block a user