Merge branch 'feat/short' into lev-exchange

This commit is contained in:
Sam Germain
2021-09-04 20:15:36 -06:00
95 changed files with 1757 additions and 1148 deletions

View File

@@ -15,6 +15,7 @@ from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
timeframe_to_seconds, validate_exchange,
validate_exchanges)
from freqtrade.exchange.ftx import Ftx
from freqtrade.exchange.gateio import Gateio
from freqtrade.exchange.hitbtc import Hitbtc
from freqtrade.exchange.kraken import Kraken
from freqtrade.exchange.kucoin import Kucoin

View File

@@ -19,7 +19,8 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU
decimal_to_precision)
from pandas import DataFrame
from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES,
ListPairsWithTimeframes)
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
from freqtrade.enums import Collateral
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
@@ -366,9 +367,16 @@ class Exchange:
def validate_stakecurrency(self, stake_currency: str) -> None:
"""
Checks stake-currency against available currencies on the exchange.
Only runs on startup. If markets have not been loaded, there's been a problem with
the connection to the exchange.
:param stake_currency: Stake-currency to validate
:raise: OperationalException if stake-currency is not available.
"""
if not self._markets:
raise OperationalException(
'Could not load markets, therefore cannot start. '
'Please investigate the above error for more details.'
)
quote_currencies = self.get_quote_currencies()
if stake_currency not in quote_currencies:
raise OperationalException(
@@ -646,6 +654,8 @@ class Exchange:
if self.exchange_has('fetchL2OrderBook'):
ob = self.fetch_l2_order_book(pair, 20)
ob_type = 'asks' if side == 'buy' else 'bids'
slippage = 0.05
max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage))
remaining_amount = amount
filled_amount = 0
@@ -654,7 +664,9 @@ class Exchange:
book_entry_coin_volume = book_entry[1]
if remaining_amount > 0:
if remaining_amount < book_entry_coin_volume:
# Orderbook at this slot bigger than remaining amount
filled_amount += remaining_amount * book_entry_price
break
else:
filled_amount += book_entry_coin_volume * book_entry_price
remaining_amount -= book_entry_coin_volume
@@ -663,7 +675,14 @@ class Exchange:
else:
# If remaining_amount wasn't consumed completely (break was not called)
filled_amount += remaining_amount * book_entry_price
forecast_avg_filled_price = filled_amount / amount
forecast_avg_filled_price = max(filled_amount, 0) / amount
# Limit max. slippage to specified value
if side == 'buy':
forecast_avg_filled_price = min(forecast_avg_filled_price, max_slippage_val)
else:
forecast_avg_filled_price = max(forecast_avg_filled_price, max_slippage_val)
return self.price_to_precision(pair, forecast_avg_filled_price)
return rate
@@ -822,7 +841,7 @@ class Exchange:
:param order: Order dict as returned from fetch_order()
:return: True if order has been cancelled without being filled, False otherwise.
"""
return (order.get('status') in ('closed', 'canceled', 'cancelled')
return (order.get('status') in NON_OPEN_EXCHANGE_STATES
and order.get('filled') == 0.0)
@retrier
@@ -1056,7 +1075,7 @@ class Exchange:
logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price")
ticker = self.fetch_ticker(pair)
ticker_rate = ticker[conf_strategy['price_side']]
if ticker['last']:
if ticker['last'] and ticker_rate:
if side == 'buy' and ticker_rate > ticker['last']:
balance = conf_strategy['ask_last_balance']
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
@@ -1271,7 +1290,7 @@ class Exchange:
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
input_coroutines = []
cached_pairs = []
# Gather coroutines to run
for pair, timeframe in set(pair_list):
if (((pair, timeframe) not in self._klines)
@@ -1283,6 +1302,7 @@ class Exchange:
"Using cached candle (OHLCV) data for pair %s, timeframe %s ...",
pair, timeframe
)
cached_pairs.append((pair, timeframe))
results = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coroutines, return_exceptions=True))
@@ -1305,6 +1325,10 @@ class Exchange:
results_df[(pair, timeframe)] = ohlcv_df
if cache:
self._klines[(pair, timeframe)] = ohlcv_df
# Return cached klines
for pair, timeframe in cached_pairs:
results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False)
return results_df
def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool:

View File

@@ -0,0 +1,23 @@
""" Gate.io exchange subclass """
import logging
from typing import Dict
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Gateio(Exchange):
"""
Gate.io exchange class. Contains adjustments needed for Freqtrade to work
with this exchange.
Please note that this exchange is not included in the list of exchanges
officially supported by the Freqtrade development team. So some features
may still not work as expected.
"""
_ft_has: Dict = {
"ohlcv_candle_limit": 1000,
}