Merge pull request #8014 from freqtrade/binance_mig

Binance futures naming migration
This commit is contained in:
Matthias 2023-01-15 21:57:21 +01:00 committed by GitHub
commit fc9e0ede0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 6542 additions and 5897 deletions

View File

@ -67,8 +67,6 @@ You will also have to pick a "margin mode" (explanation below) - with freqtrade
Freqtrade follows the [ccxt naming conventions for futures](https://docs.ccxt.com/en/latest/manual.html?#perpetual-swap-perpetual-future). Freqtrade follows the [ccxt naming conventions for futures](https://docs.ccxt.com/en/latest/manual.html?#perpetual-swap-perpetual-future).
A futures pair will therefore have the naming of `base/quote:settle` (e.g. `ETH/USDT:USDT`). A futures pair will therefore have the naming of `base/quote:settle` (e.g. `ETH/USDT:USDT`).
Binance is currently still an exception to this naming scheme, where pairs are named `ETH/USDT` also for futures markets, but will be aligned as soon as CCXT is ready.
### Margin mode ### Margin mode
On top of `trading_mode` - you will also have to configure your `margin_mode`. On top of `trading_mode` - you will also have to configure your `margin_mode`.

View File

@ -14,6 +14,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange import market_is_active, timeframe_to_minutes from freqtrade.exchange import market_is_active, timeframe_to_minutes
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver
from freqtrade.util.binance_mig import migrate_binance_futures_data
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -86,6 +87,7 @@ def start_download_data(args: Dict[str, Any]) -> None:
"Please use `--dl-trades` instead for this exchange " "Please use `--dl-trades` instead for this exchange "
"(will unfortunately take a long time)." "(will unfortunately take a long time)."
) )
migrate_binance_futures_data(config)
pairs_not_available = refresh_backtest_ohlcv_data( pairs_not_available = refresh_backtest_ohlcv_data(
exchange, pairs=expanded_pairs, timeframes=config['timeframes'], exchange, pairs=expanded_pairs, timeframes=config['timeframes'],
datadir=config['datadir'], timerange=timerange, datadir=config['datadir'], timerange=timerange,
@ -145,6 +147,7 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
""" """
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if ohlcv: if ohlcv:
migrate_binance_futures_data(config)
candle_types = [CandleType.from_string(ct) for ct in config.get('candle_types', ['spot'])] candle_types = [CandleType.from_string(ct) for ct in config.get('candle_types', ['spot'])]
for candle_type in candle_types: for candle_type in candle_types:
convert_ohlcv_format(config, convert_ohlcv_format(config,

View File

@ -374,6 +374,21 @@ class IDataHandler(ABC):
logger.warning(f"{pair}, {candle_type}, {timeframe}, " logger.warning(f"{pair}, {candle_type}, {timeframe}, "
f"data ends at {pairdata.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}") f"data ends at {pairdata.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}")
def rename_futures_data(
self, pair: str, new_pair: str, timeframe: str, candle_type: CandleType):
"""
Temporary method to migrate data from old naming to new naming (BTC/USDT -> BTC/USDT:USDT)
Only used for binance to support the binance futures naming unification.
"""
file_old = self._pair_data_filename(self._datadir, pair, timeframe, candle_type)
file_new = self._pair_data_filename(self._datadir, new_pair, timeframe, candle_type)
# print(file_old, file_new)
if file_new.exists():
logger.warning(f"{file_new} exists already, can't migrate {pair}.")
return
file_old.rename(file_new)
def get_datahandlerclass(datatype: str) -> Type[IDataHandler]: def get_datahandlerclass(datatype: str) -> Type[IDataHandler]:
""" """

View File

@ -28,7 +28,7 @@ class Binance(Exchange):
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "fromId", "trades_pagination_arg": "fromId",
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
"ccxt_futures_name": "future" "ccxt_futures_name": "swap"
} }
_ft_has_futures: Dict = { _ft_has_futures: Dict = {
"stoploss_order_types": {"limit": "stop", "market": "stop_market"}, "stoploss_order_types": {"limit": "stop", "market": "stop_market"},

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ from freqtrade.rpc.external_message_consumer import ExternalMessageConsumer
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.util import FtPrecise from freqtrade.util import FtPrecise
from freqtrade.util.binance_mig import migrate_binance_futures_names
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
@ -177,6 +178,8 @@ class FreqtradeBot(LoggingMixin):
Called on startup and after reloading the bot - triggers notifications and Called on startup and after reloading the bot - triggers notifications and
performs startup tasks performs startup tasks
""" """
migrate_binance_futures_names(self.config)
self.rpc.startup_messages(self.config, self.pairlists, self.protections) self.rpc.startup_messages(self.config, self.pairlists, self.protections)
# Update older trades with precision and precision mode # Update older trades with precision and precision mode
self.startup_backpopulate_precision() self.startup_backpopulate_precision()

View File

@ -37,6 +37,7 @@ from freqtrade.plugins.protectionmanager import ProtectionManager
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.util.binance_mig import migrate_binance_futures_data
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
@ -157,6 +158,7 @@ class Backtesting:
self._can_short = self.trading_mode != TradingMode.SPOT self._can_short = self.trading_mode != TradingMode.SPOT
self._position_stacking: bool = self.config.get('position_stacking', False) self._position_stacking: bool = self.config.get('position_stacking', False)
self.enable_protections: bool = self.config.get('enable_protections', False) self.enable_protections: bool = self.config.get('enable_protections', False)
migrate_binance_futures_data(config)
self.init_backtest() self.init_backtest()

View File

@ -0,0 +1,76 @@
import logging
from freqtrade.constants import Config
from freqtrade.enums.tradingmode import TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.persistence.pairlock import PairLock
from freqtrade.persistence.trade_model import Trade
logger = logging.getLogger(__name__)
def migrate_binance_futures_names(config: Config):
if (
not (config.get('trading_mode', TradingMode.SPOT) == TradingMode.FUTURES
and config['exchange']['name'] == 'binance')
):
# only act on new futures
return
import ccxt
if "2.6.6" > ccxt.__version__:
raise OperationalException(
"Please follow the update instructions in the docs "
"(https://www.freqtrade.io/en/latest/updating/) to install a compatible ccxt version.")
_migrate_binance_futures_db(config)
migrate_binance_futures_data(config)
def _migrate_binance_futures_db(config: Config):
logger.warning('Migrating binance futures pairs in database.')
trades = Trade.get_trades([Trade.exchange == 'binance', Trade.trading_mode == 'FUTURES']).all()
for trade in trades:
if ':' in trade.pair:
# already migrated
continue
new_pair = f"{trade.pair}:{trade.stake_currency}"
trade.pair = new_pair
for order in trade.orders:
order.ft_pair = new_pair
# Should symbol be migrated too?
# order.symbol = new_pair
Trade.commit()
pls = PairLock.query.filter(PairLock.pair.notlike('%:%'))
for pl in pls:
pl.pair = f"{pl.pair}:{config['stake_currency']}"
# print(pls)
# pls.update({'pair': concat(PairLock.pair,':USDT')})
Trade.commit()
logger.warning('Done migrating binance futures pairs in database.')
def migrate_binance_futures_data(config: Config):
if (
not (config.get('trading_mode', TradingMode.SPOT) == TradingMode.FUTURES
and config['exchange']['name'] == 'binance')
):
# only act on new futures
return
from freqtrade.data.history.idatahandler import get_datahandler
dhc = get_datahandler(config['datadir'], config.get('dataformat_ohlcv', 'json'))
paircombs = dhc.ohlcv_get_available_data(
config['datadir'],
config.get('trading_mode', TradingMode.SPOT)
)
for pair, timeframe, candle_type in paircombs:
if ':' in pair:
# already migrated
continue
new_pair = f"{pair}:{config['stake_currency']}"
dhc.rename_futures_data(pair, new_pair, timeframe, candle_type)

View File

@ -2,7 +2,7 @@ numpy==1.24.1
pandas==1.5.2 pandas==1.5.2
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==2.5.56 ccxt==2.6.6
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==38.0.1; platform_machine == 'armv7l' cryptography==38.0.1; platform_machine == 'armv7l'
cryptography==38.0.4; platform_machine != 'armv7l' cryptography==38.0.4; platform_machine != 'armv7l'

View File

@ -60,7 +60,7 @@ setup(
], ],
install_requires=[ install_requires=[
# from requirements.txt # from requirements.txt
'ccxt>=1.92.9', 'ccxt>=2.6.6',
'SQLAlchemy', 'SQLAlchemy',
'python-telegram-bot>=13.4', 'python-telegram-bot>=13.4',
'arrow>=0.17.0', 'arrow>=0.17.0',

View File

@ -1451,9 +1451,9 @@ def test_start_list_data(testdatadir, capsys):
captured = capsys.readouterr() captured = capsys.readouterr()
assert "Found 5 pair / timeframe combinations." in captured.out assert "Found 5 pair / timeframe combinations." in captured.out
assert "\n| Pair | Timeframe | Type |\n" in captured.out assert "\n| Pair | Timeframe | Type |\n" in captured.out
assert "\n| XRP/USDT | 1h | futures |\n" in captured.out assert "\n| XRP/USDT:USDT | 1h | futures |\n" in captured.out
assert "\n| XRP/USDT | 1h, 8h | mark |\n" in captured.out assert "\n| XRP/USDT:USDT | 1h, 8h | mark |\n" in captured.out
args = [ args = [
"list-data", "list-data",

View File

@ -3109,7 +3109,7 @@ def funding_rate_history_octohourly():
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def leverage_tiers(): def leverage_tiers():
return { return {
"1000SHIB/USDT": [ "1000SHIB/USDT:USDT": [
{ {
'minNotional': 0, 'minNotional': 0,
'maxNotional': 50000, 'maxNotional': 50000,
@ -3160,7 +3160,7 @@ def leverage_tiers():
'maintAmt': 654500.0 'maintAmt': 654500.0
}, },
], ],
"1INCH/USDT": [ "1INCH/USDT:USDT": [
{ {
'minNotional': 0, 'minNotional': 0,
'maxNotional': 5000, 'maxNotional': 5000,
@ -3204,7 +3204,7 @@ def leverage_tiers():
'maintAmt': 386940.0 'maintAmt': 386940.0
}, },
], ],
"AAVE/USDT": [ "AAVE/USDT:USDT": [
{ {
'minNotional': 0, 'minNotional': 0,
'maxNotional': 5000, 'maxNotional': 5000,
@ -3248,7 +3248,7 @@ def leverage_tiers():
'maintAmt': 386950.0 'maintAmt': 386950.0
}, },
], ],
"ADA/BUSD": [ "ADA/BUSD:BUSD": [
{ {
"minNotional": 0, "minNotional": 0,
"maxNotional": 100000, "maxNotional": 100000,
@ -3292,7 +3292,7 @@ def leverage_tiers():
"maintAmt": 1527500.0 "maintAmt": 1527500.0
}, },
], ],
'BNB/BUSD': [ 'BNB/BUSD:BUSD': [
{ {
"minNotional": 0, # stake(before leverage) = 0 "minNotional": 0, # stake(before leverage) = 0
"maxNotional": 100000, # max stake(before leverage) = 5000 "maxNotional": 100000, # max stake(before leverage) = 5000
@ -3336,7 +3336,7 @@ def leverage_tiers():
"maintAmt": 1527500.0 "maintAmt": 1527500.0
} }
], ],
'BNB/USDT': [ 'BNB/USDT:USDT': [
{ {
"minNotional": 0, # stake = 0.0 "minNotional": 0, # stake = 0.0
"maxNotional": 10000, # max_stake = 133.33333333333334 "maxNotional": 10000, # max_stake = 133.33333333333334
@ -3401,7 +3401,7 @@ def leverage_tiers():
"maintAmt": 6233035.0 "maintAmt": 6233035.0
}, },
], ],
'BTC/USDT': [ 'BTC/USDT:USDT': [
{ {
"minNotional": 0, # stake = 0.0 "minNotional": 0, # stake = 0.0
"maxNotional": 50000, # max_stake = 400.0 "maxNotional": 50000, # max_stake = 400.0
@ -3473,7 +3473,7 @@ def leverage_tiers():
"maintAmt": 1.997038E8 "maintAmt": 1.997038E8
}, },
], ],
"ZEC/USDT": [ "ZEC/USDT:USDT": [
{ {
'minNotional': 0, 'minNotional': 0,
'maxNotional': 50000, 'maxNotional': 50000,

View File

@ -294,8 +294,8 @@ def test_convert_trades_format(default_conf, testdatadir, tmpdir):
@pytest.mark.parametrize('file_base,candletype', [ @pytest.mark.parametrize('file_base,candletype', [
(['XRP_ETH-5m', 'XRP_ETH-1m'], CandleType.SPOT), (['XRP_ETH-5m', 'XRP_ETH-1m'], CandleType.SPOT),
(['UNITTEST_USDT-1h-mark', 'XRP_USDT-1h-mark'], CandleType.MARK), (['UNITTEST_USDT_USDT-1h-mark', 'XRP_USDT_USDT-1h-mark'], CandleType.MARK),
(['XRP_USDT-1h-futures'], CandleType.FUTURES), (['XRP_USDT_USDT-1h-futures'], CandleType.FUTURES),
]) ])
def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, candletype): def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, candletype):
tmpdir1 = Path(tmpdir) tmpdir1 = Path(tmpdir)
@ -315,7 +315,10 @@ def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, cand
files_new.append(file_new) files_new.append(file_new)
default_conf['datadir'] = tmpdir1 default_conf['datadir'] = tmpdir1
default_conf['pairs'] = ['XRP_ETH', 'XRP_USDT', 'UNITTEST_USDT'] if candletype == CandleType.SPOT:
default_conf['pairs'] = ['XRP/ETH', 'XRP/USDT', 'UNITTEST/USDT']
else:
default_conf['pairs'] = ['XRP/ETH:ETH', 'XRP/USDT:USDT', 'UNITTEST/USDT:USDT']
default_conf['timeframes'] = ['1m', '5m', '1h'] default_conf['timeframes'] = ['1m', '5m', '1h']
assert not file_new.exists() assert not file_new.exists()

View File

@ -33,10 +33,10 @@ def test_datahandler_ohlcv_get_pairs(testdatadir):
assert set(pairs) == {'UNITTEST/BTC'} assert set(pairs) == {'UNITTEST/BTC'}
pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.MARK) pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.MARK)
assert set(pairs) == {'UNITTEST/USDT', 'XRP/USDT'} assert set(pairs) == {'UNITTEST/USDT:USDT', 'XRP/USDT:USDT'}
pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.FUTURES) pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.FUTURES)
assert set(pairs) == {'XRP/USDT'} assert set(pairs) == {'XRP/USDT:USDT'}
pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.MARK) pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.MARK)
assert set(pairs) == {'UNITTEST/USDT:USDT'} assert set(pairs) == {'UNITTEST/USDT:USDT'}
@ -104,11 +104,11 @@ def test_datahandler_ohlcv_get_available_data(testdatadir):
paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.FUTURES) paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.FUTURES)
# Convert to set to avoid failures due to sorting # Convert to set to avoid failures due to sorting
assert set(paircombs) == { assert set(paircombs) == {
('UNITTEST/USDT', '1h', 'mark'), ('UNITTEST/USDT:USDT', '1h', 'mark'),
('XRP/USDT', '1h', 'futures'), ('XRP/USDT:USDT', '1h', 'futures'),
('XRP/USDT', '1h', 'mark'), ('XRP/USDT:USDT', '1h', 'mark'),
('XRP/USDT', '8h', 'mark'), ('XRP/USDT:USDT', '8h', 'mark'),
('XRP/USDT', '8h', 'funding_rate'), ('XRP/USDT:USDT', '8h', 'funding_rate'),
} }
paircombs = JsonGzDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.SPOT) paircombs = JsonGzDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.SPOT)
@ -142,7 +142,7 @@ def test_jsondatahandler_ohlcv_load(testdatadir, caplog):
df = dh.ohlcv_load('XRP/ETH', '5m', 'spot') df = dh.ohlcv_load('XRP/ETH', '5m', 'spot')
assert len(df) == 712 assert len(df) == 712
df_mark = dh.ohlcv_load('UNITTEST/USDT', '1h', candle_type="mark") df_mark = dh.ohlcv_load('UNITTEST/USDT:USDT', '1h', candle_type="mark")
assert len(df_mark) == 100 assert len(df_mark) == 100
df_no_mark = dh.ohlcv_load('UNITTEST/USDT', '1h', 'spot') df_no_mark = dh.ohlcv_load('UNITTEST/USDT', '1h', 'spot')
@ -424,7 +424,7 @@ def test_hdf5datahandler_ohlcv_load_and_resave(
# Data goes from 2018-01-10 - 2018-01-30 # Data goes from 2018-01-10 - 2018-01-30
('UNITTEST/BTC', '5m', 'spot', '', '2018-01-15', '2018-01-19'), ('UNITTEST/BTC', '5m', 'spot', '', '2018-01-15', '2018-01-19'),
# Mark data goes from to 2021-11-15 2021-11-19 # Mark data goes from to 2021-11-15 2021-11-19
('UNITTEST/USDT', '1h', 'mark', '-mark', '2021-11-16', '2021-11-18'), ('UNITTEST/USDT:USDT', '1h', 'mark', '-mark', '2021-11-16', '2021-11-18'),
]) ])
@pytest.mark.parametrize('datahandler', ['hdf5', 'feather', 'parquet']) @pytest.mark.parametrize('datahandler', ['hdf5', 'feather', 'parquet'])
def test_generic_datahandler_ohlcv_load_and_resave( def test_generic_datahandler_ohlcv_load_and_resave(

View File

@ -78,11 +78,11 @@ def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) ->
def test_load_data_mark(ohlcv_history, mocker, caplog, testdatadir) -> None: def test_load_data_mark(ohlcv_history, mocker, caplog, testdatadir) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history) mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history)
file = testdatadir / 'futures/UNITTEST_USDT-1h-mark.json' file = testdatadir / 'futures/UNITTEST_USDT_USDT-1h-mark.json'
load_data(datadir=testdatadir, timeframe='1h', pairs=['UNITTEST/BTC'], candle_type='mark') load_data(datadir=testdatadir, timeframe='1h', pairs=['UNITTEST/BTC'], candle_type='mark')
assert file.is_file() assert file.is_file()
assert not log_has( assert not log_has(
'Download history data for pair: "UNITTEST/USDT", interval: 1m ' 'Download history data for pair: "UNITTEST/USDT:USDT", interval: 1m '
'and store in None.', caplog 'and store in None.', caplog
) )

View File

@ -575,25 +575,13 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, c
assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog) assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog)
@pytest.mark.parametrize("trading_mode,margin_mode,config", [
("spot", "", {}),
("margin", "cross", {"options": {"defaultType": "margin"}}),
("futures", "isolated", {"options": {"defaultType": "future"}}),
])
def test__ccxt_config(default_conf, mocker, trading_mode, margin_mode, config):
default_conf['trading_mode'] = trading_mode
default_conf['margin_mode'] = margin_mode
exchange = get_patched_exchange(mocker, default_conf, id="binance")
assert exchange._ccxt_config == config
@pytest.mark.parametrize('pair,nominal_value,mm_ratio,amt', [ @pytest.mark.parametrize('pair,nominal_value,mm_ratio,amt', [
("BNB/BUSD", 0.0, 0.025, 0), ("BNB/BUSD:BUSD", 0.0, 0.025, 0),
("BNB/USDT", 100.0, 0.0065, 0), ("BNB/USDT:USDT", 100.0, 0.0065, 0),
("BTC/USDT", 170.30, 0.004, 0), ("BTC/USDT:USDT", 170.30, 0.004, 0),
("BNB/BUSD", 999999.9, 0.1, 27500.0), ("BNB/BUSD:BUSD", 999999.9, 0.1, 27500.0),
("BNB/USDT", 5000000.0, 0.15, 233035.0), ("BNB/USDT:USDT", 5000000.0, 0.15, 233035.0),
("BTC/USDT", 600000000, 0.5, 1.997038E8), ("BTC/USDT:USDT", 600000000, 0.5, 1.997038E8),
]) ])
def test_get_maintenance_ratio_and_amt_binance( def test_get_maintenance_ratio_and_amt_binance(
default_conf, default_conf,

View File

@ -37,6 +37,7 @@ EXCHANGES = {
# 'hasQuoteVolume': True, # 'hasQuoteVolume': True,
# 'timeframe': '5m', # 'timeframe': '5m',
# 'futures': True, # 'futures': True,
# 'futures_pair': 'BTC/USDT:USDT',
# 'leverage_tiers_public': False, # 'leverage_tiers_public': False,
# 'leverage_in_spot_market': False, # 'leverage_in_spot_market': False,
# }, # },

View File

@ -3957,7 +3957,7 @@ def test_validate_trading_mode_and_margin_mode(
@pytest.mark.parametrize("exchange_name,trading_mode,ccxt_config", [ @pytest.mark.parametrize("exchange_name,trading_mode,ccxt_config", [
("binance", "spot", {}), ("binance", "spot", {}),
("binance", "margin", {"options": {"defaultType": "margin"}}), ("binance", "margin", {"options": {"defaultType": "margin"}}),
("binance", "futures", {"options": {"defaultType": "future"}}), ("binance", "futures", {"options": {"defaultType": "swap"}}),
("bybit", "spot", {"options": {"defaultType": "spot"}}), ("bybit", "spot", {"options": {"defaultType": "spot"}}),
("bybit", "futures", {"options": {"defaultType": "linear"}}), ("bybit", "futures", {"options": {"defaultType": "linear"}}),
("gateio", "futures", {"options": {"defaultType": "swap"}}), ("gateio", "futures", {"options": {"defaultType": "swap"}}),
@ -4898,22 +4898,22 @@ def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage
OperationalException, OperationalException,
match='nominal value can not be lower than 0', match='nominal value can not be lower than 0',
): ):
exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT', -1) exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT:USDT', -1)
exchange._leverage_tiers = {} exchange._leverage_tiers = {}
with pytest.raises( with pytest.raises(
InvalidOrderException, InvalidOrderException,
match="Maintenance margin rate for 1000SHIB/USDT is unavailable for", match="Maintenance margin rate for 1000SHIB/USDT:USDT is unavailable for",
): ):
exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT', 10000) exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT:USDT', 10000)
@pytest.mark.parametrize('pair,value,mmr,maintAmt', [ @pytest.mark.parametrize('pair,value,mmr,maintAmt', [
('ADA/BUSD', 500, 0.025, 0.0), ('ADA/BUSD:BUSD', 500, 0.025, 0.0),
('ADA/BUSD', 20000000, 0.5, 1527500.0), ('ADA/BUSD:BUSD', 20000000, 0.5, 1527500.0),
('ZEC/USDT', 500, 0.01, 0.0), ('ZEC/USDT:USDT', 500, 0.01, 0.0),
('ZEC/USDT', 20000000, 0.5, 654500.0), ('ZEC/USDT:USDT', 20000000, 0.5, 654500.0),
]) ])
def test_get_maintenance_ratio_and_amt( def test_get_maintenance_ratio_and_amt(
mocker, mocker,
@ -4946,21 +4946,21 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
exchange._leverage_tiers = leverage_tiers exchange._leverage_tiers = leverage_tiers
assert exchange.get_max_leverage("BNB/BUSD", 1.0) == 20.0 assert exchange.get_max_leverage("BNB/BUSD:BUSD", 1.0) == 20.0
assert exchange.get_max_leverage("BNB/USDT", 100.0) == 75.0 assert exchange.get_max_leverage("BNB/USDT:USDT", 100.0) == 75.0
assert exchange.get_max_leverage("BTC/USDT", 170.30) == 125.0 assert exchange.get_max_leverage("BTC/USDT:USDT", 170.30) == 125.0
assert pytest.approx(exchange.get_max_leverage("BNB/BUSD", 99999.9)) == 5.000005 assert pytest.approx(exchange.get_max_leverage("BNB/BUSD:BUSD", 99999.9)) == 5.000005
assert pytest.approx(exchange.get_max_leverage("BNB/USDT", 1500)) == 33.333333333333333 assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 33.333333333333333
assert exchange.get_max_leverage("BTC/USDT", 300000000) == 2.0 assert exchange.get_max_leverage("BTC/USDT:USDT", 300000000) == 2.0
assert exchange.get_max_leverage("BTC/USDT", 600000000) == 1.0 # Last tier assert exchange.get_max_leverage("BTC/USDT:USDT", 600000000) == 1.0 # Last tier
assert exchange.get_max_leverage("SPONGE/USDT", 200) == 1.0 # Pair not in leverage_tiers assert exchange.get_max_leverage("SPONGE/USDT:USDT", 200) == 1.0 # Pair not in leverage_tiers
assert exchange.get_max_leverage("BTC/USDT", 0.0) == 125.0 # No stake amount assert exchange.get_max_leverage("BTC/USDT:USDT", 0.0) == 125.0 # No stake amount
with pytest.raises( with pytest.raises(
InvalidOrderException, InvalidOrderException,
match=r'Amount 1000000000.01 too high for BTC/USDT' match=r'Amount 1000000000.01 too high for BTC/USDT:USDT'
): ):
exchange.get_max_leverage("BTC/USDT", 1000000000.01) exchange.get_max_leverage("BTC/USDT:USDT", 1000000000.01)
@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gateio', 'okx']) @pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gateio', 'okx'])

View File

@ -195,12 +195,12 @@ def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers):
exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange = get_patched_exchange(mocker, default_conf, id="okx")
exchange._leverage_tiers = leverage_tiers exchange._leverage_tiers = leverage_tiers
assert exchange.get_max_pair_stake_amount('BNB/BUSD', 1.0) == 30000000 assert exchange.get_max_pair_stake_amount('BNB/BUSD:BUSD', 1.0) == 30000000
assert exchange.get_max_pair_stake_amount('BNB/USDT', 1.0) == 50000000 assert exchange.get_max_pair_stake_amount('BNB/USDT:USDT', 1.0) == 50000000
assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0) == 1000000000 assert exchange.get_max_pair_stake_amount('BTC/USDT:USDT', 1.0) == 1000000000
assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0, 10.0) == 100000000 assert exchange.get_max_pair_stake_amount('BTC/USDT:USDT', 1.0, 10.0) == 100000000
assert exchange.get_max_pair_stake_amount('TTT/USDT', 1.0) == float('inf') # Not in tiers assert exchange.get_max_pair_stake_amount('TTT/USDT:USDT', 1.0) == float('inf') # Not in tiers
@pytest.mark.parametrize('mode,side,reduceonly,result', [ @pytest.mark.parametrize('mode,side,reduceonly,result', [

View File

@ -549,6 +549,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
default_conf_usdt['trading_mode'] = 'futures' default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated' default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt['stake_currency'] = 'USDT' default_conf_usdt['stake_currency'] = 'USDT'
default_conf_usdt['datadir'] = Path(default_conf_usdt['datadir'])
default_conf_usdt['exchange']['pair_whitelist'] = ['.*'] default_conf_usdt['exchange']['pair_whitelist'] = ['.*']
backtesting = Backtesting(default_conf_usdt) backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
@ -1460,7 +1461,7 @@ def test_backtest_start_futures_noliq(default_conf_usdt, mocker,
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['HULUMULU/USDT', 'XRP/USDT'])) PropertyMock(return_value=['HULUMULU/USDT', 'XRP/USDT:USDT']))
# mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) # mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
patched_configuration_load_config_file(mocker, default_conf_usdt) patched_configuration_load_config_file(mocker, default_conf_usdt)
@ -1491,7 +1492,7 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
"strategy": CURRENT_TEST_STRATEGY, "strategy": CURRENT_TEST_STRATEGY,
}) })
patch_exchange(mocker) patch_exchange(mocker)
result1 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT'], result1 = pd.DataFrame({'pair': ['XRP/USDT:USDT', 'XRP/USDT:USDT'],
'profit_ratio': [0.0, 0.0], 'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0], 'profit_abs': [0.0, 0.0],
'open_date': pd.to_datetime(['2021-11-18 18:00:00', 'open_date': pd.to_datetime(['2021-11-18 18:00:00',
@ -1507,7 +1508,7 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
'close_rate': [0.104969, 0.103541], 'close_rate': [0.104969, 0.103541],
'exit_reason': [ExitType.ROI, ExitType.ROI] 'exit_reason': [ExitType.ROI, ExitType.ROI]
}) })
result2 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT', 'XRP/USDT'], result2 = pd.DataFrame({'pair': ['XRP/USDT:USDT', 'XRP/USDT:USDT', 'XRP/USDT:USDT'],
'profit_ratio': [0.03, 0.01, 0.1], 'profit_ratio': [0.03, 0.01, 0.1],
'profit_abs': [0.01, 0.02, 0.2], 'profit_abs': [0.01, 0.02, 0.2],
'open_date': pd.to_datetime(['2021-11-19 18:00:00', 'open_date': pd.to_datetime(['2021-11-19 18:00:00',
@ -1552,7 +1553,7 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
} }
]) ])
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['XRP/USDT'])) PropertyMock(return_value=['XRP/USDT:USDT']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
patched_configuration_load_config_file(mocker, default_conf_usdt) patched_configuration_load_config_file(mocker, default_conf_usdt)
@ -1575,8 +1576,8 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
'up to 2021-11-21 04:00:00 (4 days).', 'up to 2021-11-21 04:00:00 (4 days).',
'Backtesting with data from 2021-11-17 21:00:00 ' 'Backtesting with data from 2021-11-17 21:00:00 '
'up to 2021-11-21 04:00:00 (3 days).', 'up to 2021-11-21 04:00:00 (3 days).',
'XRP/USDT, funding_rate, 8h, data starts at 2021-11-18 00:00:00', 'XRP/USDT:USDT, funding_rate, 8h, data starts at 2021-11-18 00:00:00',
'XRP/USDT, mark, 8h, data starts at 2021-11-18 00:00:00', 'XRP/USDT:USDT, mark, 8h, data starts at 2021-11-18 00:00:00',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
] ]

View File

@ -1553,13 +1553,13 @@ def test_list_available_pairs(botclient):
client, f"{BASE_URI}/available_pairs?timeframe=1h") client, f"{BASE_URI}/available_pairs?timeframe=1h")
assert_response(rc) assert_response(rc)
assert rc.json()['length'] == 1 assert rc.json()['length'] == 1
assert rc.json()['pairs'] == ['XRP/USDT'] assert rc.json()['pairs'] == ['XRP/USDT:USDT']
rc = client_get( rc = client_get(
client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark") client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark")
assert_response(rc) assert_response(rc)
assert rc.json()['length'] == 2 assert rc.json()['length'] == 2
assert rc.json()['pairs'] == ['UNITTEST/USDT', 'XRP/USDT'] assert rc.json()['pairs'] == ['UNITTEST/USDT:USDT', 'XRP/USDT:USDT']
assert len(rc.json()['pair_interval']) == 2 assert len(rc.json()['pair_interval']) == 2

59
tests/test_binance_mig.py Normal file
View File

@ -0,0 +1,59 @@
import shutil
from pathlib import Path
import pytest
from freqtrade.persistence import Trade
from freqtrade.util.binance_mig import migrate_binance_futures_data, migrate_binance_futures_names
from tests.conftest import create_mock_trades_usdt, log_has
def test_binance_mig_data_conversion(default_conf_usdt, tmpdir, testdatadir):
# call doing nothing (spot mode)
migrate_binance_futures_data(default_conf_usdt)
default_conf_usdt['trading_mode'] = 'futures'
pair_old = 'XRP_USDT'
pair_unified = 'XRP_USDT_USDT'
futures_src = testdatadir / 'futures'
futures_dst = tmpdir / 'futures'
futures_dst.mkdir()
files = [
'-1h-mark.json',
'-1h-futures.json',
'-8h-funding_rate.json',
'-8h-mark.json',
]
# Copy files to tmpdir and rename to old naming
for file in files:
fn_after = futures_dst / f'{pair_old}{file}'
shutil.copy(futures_src / f'{pair_unified}{file}', fn_after)
default_conf_usdt['datadir'] = Path(tmpdir)
# Migrate files to unified namings
migrate_binance_futures_data(default_conf_usdt)
for file in files:
fn_after = futures_dst / f'{pair_unified}{file}'
assert fn_after.exists()
@pytest.mark.usefixtures("init_persistence")
def test_binance_mig_db_conversion(default_conf_usdt, fee, caplog):
# Does nothing in spot mode
migrate_binance_futures_names(default_conf_usdt)
create_mock_trades_usdt(fee, None)
for t in Trade.get_trades():
t.trading_mode = 'FUTURES'
t.exchange = 'binance'
Trade.commit()
default_conf_usdt['datadir'] = Path(default_conf_usdt['datadir'])
default_conf_usdt['trading_mode'] = 'futures'
migrate_binance_futures_names(default_conf_usdt)
assert log_has('Migrating binance futures pairs in database.', caplog)