From fc6d7f012e6c0cdebbd3aac034bbb044d18189b9 Mon Sep 17 00:00:00 2001 From: pbs Date: Mon, 13 Mar 2023 17:34:34 +0000 Subject: [PATCH 01/53] Support for python 3.11 --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index a9ff36536..4c3fb945b 100755 --- a/setup.sh +++ b/setup.sh @@ -25,7 +25,7 @@ function check_installed_python() { exit 2 fi - for v in 10 9 8 + for v in 11 10 9 8 do PYTHON="python3.${v}" which $PYTHON From 531b5727f24c40502ed08d3be989126fa530d2c1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 14:33:34 +0200 Subject: [PATCH 02/53] add fetch_orders exchange wrapper --- freqtrade/exchange/exchange.py | 23 +++++++++++++++++++++++ tests/exchange/test_exchange.py | 26 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9a303426a..822d1074b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1432,6 +1432,29 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + @retrier(retries=0) + def fetch_orders(self, pair: str, since: datetime) -> List[Dict]: + """ + Fetch all orders for a pair "since" + :param pair: Pair for the query + :param since: Starting time for the query + """ + if self._config['dry_run'] or not self.exchange_has('fetchOrders'): + return [] + try: + since_ms = int((since.timestamp() - 10) * 1000) + orders: List[Dict] = self._api.fetch_orders(pair, since=since_ms) + self._log_exchange_response('fetch_orders', orders) + orders = [self._order_contracts_to_amount(o) for o in orders] + return orders + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not fetch positions due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + @retrier def fetch_trading_fees(self) -> Dict[str, Any]: """ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b0760944a..0452f70e3 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1773,6 +1773,32 @@ def test_fetch_positions(default_conf, mocker, exchange_name): "fetch_positions", "fetch_positions") +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_fetch_orders(default_conf, mocker, exchange_name, limit_order): + + api_mock = MagicMock() + api_mock.fetch_orders = MagicMock(return_value=[ + limit_order['buy'], + limit_order['sell'], + ]) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) + start_time = datetime.now(timezone.utc) - timedelta(days=5) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + # Not available in dry-run + assert exchange.fetch_orders('mocked', start_time) == [] + + default_conf['dry_run'] = False + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + res = exchange.fetch_orders('mocked', start_time) + assert len(res) == 2 + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + "fetch_orders", "fetch_orders", retries=1, + pair='mocked', since=start_time) + + def test_fetch_trading_fees(default_conf, mocker): api_mock = MagicMock() tick = { From d14f50f50db83e2250e6737ea5b47883bf7aabf9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 14:39:18 +0200 Subject: [PATCH 03/53] temporary comment fetch_orders logic --- freqtrade/exchange/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 42a7094ba..3a4a940ca 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -84,6 +84,7 @@ EXCHANGE_HAS_OPTIONAL = [ # 'fetchPositions', # Futures trading # 'fetchLeverageTiers', # Futures initialization # 'fetchMarketLeverageTiers', # Futures initialization + # 'fetchOpenOrders', 'fetchClosedOrders', # 'fetchOrders', # Refinding balance... ] From 81633b7c2ee0b30de77c2e62b2e34f753c0f805c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 15:35:53 +0200 Subject: [PATCH 04/53] Add "handle_onexchange_order" functionality --- freqtrade/freqtradebot.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 89f0ac55d..ae569f7c2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -451,6 +451,35 @@ class FreqtradeBot(LoggingMixin): except ExchangeError: logger.warning(f"Error updating {order.order_id}.") + def handle_onexchange_order(self, trade: Trade): + """ + Try refinding a order that is not in the database. + Only used balance disappeared, which would make exiting impossible. + """ + try: + orders = self.exchange.fetch_orders(trade.pair, trade.open_date_utc) + for order in orders: + trade_order = [o for o in trade.orders if o.order_id == order['id']] + if trade_order: + continue + logger.info(f"Found previously unknown order {order['id']} for {trade.pair}.") + order_obj = Order.parse_from_ccxt_object(order, trade.pair, order['side']) + order_obj.order_filled_date = datetime.fromtimestamp( + safe_value_fallback(order, 'lastTradeTimestamp', 'timestamp') // 1000, + tz=timezone.utc) + trade.orders.append(order_obj) + # TODO: how do we handle open_order_id ... + Trade.commit() + self.update_trade_state(trade, order['id'], order) + logger.info(f"handled order {order['id']}") + if not trade.is_open: + # Trade was just closed + trade.close_date = order_obj.order_filled_date + Trade.commit() + continue + + except ExchangeError: + logger.warning("Error finding onexchange order") # # BUY / enter positions / open trades logic and methods # @@ -1034,6 +1063,16 @@ class FreqtradeBot(LoggingMixin): """ trades_closed = 0 for trade in trades: + # TODO: get_total currently fails for futures! + wallet_amount = self.wallets.get_total(trade.safe_base_currency) + + if wallet_amount < trade.amount: + # + logger.warning( + f'Not enough {trade.safe_base_currency} in wallet to exit {trade.pair}. ' + f'Amount needed: {trade.amount}, amount available: {wallet_amount}') + self.handle_onexchange_order(trade) + try: try: if (self.strategy.order_types.get('stoploss_on_exchange') and From 95b35e452d9a2043e2b82742adc389afc1bae546 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 17:13:02 +0200 Subject: [PATCH 05/53] Emulate fetch_orders if it ain't supported natively --- freqtrade/exchange/exchange.py | 22 ++++++++++++++++-- tests/exchange/test_exchange.py | 41 ++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 822d1074b..07abb489f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1439,11 +1439,29 @@ class Exchange: :param pair: Pair for the query :param since: Starting time for the query """ - if self._config['dry_run'] or not self.exchange_has('fetchOrders'): + if self._config['dry_run']: return [] + + def fetch_orders_emulate() -> List[Dict]: + orders = [] + if self.exchange_has('fetchClosedOrders'): + orders = self._api.fetch_closed_orders(pair, since=since_ms) + if self.exchange_has('fetchOpenOrders'): + orders_open = self._api.fetch_open_orders(pair, since=since_ms) + orders.extend(orders_open) + return orders + try: since_ms = int((since.timestamp() - 10) * 1000) - orders: List[Dict] = self._api.fetch_orders(pair, since=since_ms) + if self.exchange_has('fetchOrders'): + try: + orders: List[Dict] = self._api.fetch_orders(pair, since=since_ms) + except ccxt.NotSupported: + # Some exchanges don't support fetchOrders + # attempt to fetch open and closed orders separately + orders = fetch_orders_emulate() + else: + orders = fetch_orders_emulate() self._log_exchange_response('fetch_orders', orders) orders = [self._order_contracts_to_amount(o) for o in orders] return orders diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 0452f70e3..5994c56e0 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1781,23 +1781,62 @@ def test_fetch_orders(default_conf, mocker, exchange_name, limit_order): limit_order['buy'], limit_order['sell'], ]) + api_mock.fetch_open_orders = MagicMock(return_value=[limit_order['buy']]) + api_mock.fetch_closed_orders = MagicMock(return_value=[limit_order['buy']]) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) start_time = datetime.now(timezone.utc) - timedelta(days=5) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # Not available in dry-run assert exchange.fetch_orders('mocked', start_time) == [] - + assert api_mock.fetch_orders.call_count == 0 default_conf['dry_run'] = False exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) res = exchange.fetch_orders('mocked', start_time) + assert api_mock.fetch_orders.call_count == 1 + assert api_mock.fetch_open_orders.call_count == 0 + assert api_mock.fetch_closed_orders.call_count == 0 assert len(res) == 2 + res = exchange.fetch_orders('mocked', start_time) + + api_mock.fetch_orders.reset_mock() + + def has_resp(_, endpoint): + if endpoint == 'fetchOrders': + return False + if endpoint == 'fetchClosedOrders': + return True + if endpoint == 'fetchOpenOrders': + return True + + mocker.patch(f'{EXMS}.exchange_has', has_resp) + + # happy path without fetchOrders + res = exchange.fetch_orders('mocked', start_time) + assert api_mock.fetch_orders.call_count == 0 + assert api_mock.fetch_open_orders.call_count == 1 + assert api_mock.fetch_closed_orders.call_count == 1 + + mocker.patch(f'{EXMS}.exchange_has', return_value=True) + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "fetch_orders", "fetch_orders", retries=1, pair='mocked', since=start_time) + # Unhappy path - first fetch-orders call fails. + api_mock.fetch_orders = MagicMock(side_effect=ccxt.NotSupported()) + api_mock.fetch_open_orders.reset_mock() + api_mock.fetch_closed_orders.reset_mock() + + res = exchange.fetch_orders('mocked', start_time) + + assert api_mock.fetch_orders.call_count == 1 + assert api_mock.fetch_open_orders.call_count == 1 + assert api_mock.fetch_closed_orders.call_count == 1 + def test_fetch_trading_fees(default_conf, mocker): api_mock = MagicMock() From 974cf6c365348515d3d3ca4cdaabd628a4c0f7f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 17:41:59 +0200 Subject: [PATCH 06/53] Move comment to more appropriate spot --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ae569f7c2..5a253e40c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1575,13 +1575,13 @@ class FreqtradeBot(LoggingMixin): # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() if self.trading_mode == TradingMode.FUTURES: + # A safe exit amount isn't needed for futures, you can just exit/close the position return amount trade_base_currency = self.exchange.get_pair_base_currency(pair) wallet_amount = self.wallets.get_free(trade_base_currency) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount >= amount: - # A safe exit amount isn't needed for futures, you can just exit/close the position return amount elif wallet_amount > amount * 0.98: logger.info(f"{pair} - Falling back to wallet-amount {wallet_amount} -> {amount}.") From 24cab004790a9073bc45ce9f249910a38a8303ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 17:46:58 +0200 Subject: [PATCH 07/53] Extract amount checking to wallets, implement for futures --- freqtrade/freqtradebot.py | 9 +++------ freqtrade/wallets.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5a253e40c..444fe044a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1063,14 +1063,11 @@ class FreqtradeBot(LoggingMixin): """ trades_closed = 0 for trade in trades: - # TODO: get_total currently fails for futures! - wallet_amount = self.wallets.get_total(trade.safe_base_currency) - if wallet_amount < trade.amount: - # + if not self.wallets.check_exit_amount(trade): logger.warning( - f'Not enough {trade.safe_base_currency} in wallet to exit {trade.pair}. ' - f'Amount needed: {trade.amount}, amount available: {wallet_amount}') + f'Not enough {trade.safe_base_currency} in wallet to exit {trade}. ' + 'Trying to recover.') self.handle_onexchange_order(trade) try: diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 6f86398f3..ecac638c6 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -181,6 +181,35 @@ class Wallets: def get_all_positions(self) -> Dict[str, PositionWallet]: return self._positions + def _check_exit_amount(self, trade: Trade) -> bool: + if trade.trading_mode != TradingMode.FUTURES: + # Slightly higher offset than in safe_exit_amount. + wallet_amount: float = self.get_total(trade.safe_base_currency) * 0.981 + else: + # wallet_amount: float = self.wallets.get_free(trade.safe_base_currency) + position = self._positions.get(trade.pair) + if position is None: + # We don't own anything :O + return False + wallet_amount = position.position + + if wallet_amount >= trade.amount: + return True + return False + + def check_exit_amount(self, trade: Trade) -> bool: + """ + Checks if the exit amount is available in the wallet. + :param trade: Trade to check + :return: True if the exit amount is available, False otherwise + """ + if not self._check_exit_amount(trade): + # Update wallets just to make sure + self.update() + return self._check_exit_amount(trade) + + return True + def get_starting_balance(self) -> float: """ Retrieves starting balance - based on either available capital, From f2696c96095ffe20e10e03d3697168ac7cf8bc57 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 18:09:46 +0200 Subject: [PATCH 08/53] Force special exit reason for "recovered" exits --- freqtrade/enums/exittype.py | 1 + freqtrade/freqtradebot.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/enums/exittype.py b/freqtrade/enums/exittype.py index b025230ba..c21b62667 100644 --- a/freqtrade/enums/exittype.py +++ b/freqtrade/enums/exittype.py @@ -15,6 +15,7 @@ class ExitType(Enum): EMERGENCY_EXIT = "emergency_exit" CUSTOM_EXIT = "custom_exit" PARTIAL_EXIT = "partial_exit" + SOLD_ON_EXCHANGE = "sold_on_exchange" NONE = "" def __str__(self): diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 444fe044a..8b877541c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -470,13 +470,17 @@ class FreqtradeBot(LoggingMixin): trade.orders.append(order_obj) # TODO: how do we handle open_order_id ... Trade.commit() + prev_exit_reason = trade.exit_reason + trade.exit_reason = ExitType.SOLD_ON_EXCHANGE.value self.update_trade_state(trade, order['id'], order) logger.info(f"handled order {order['id']}") if not trade.is_open: # Trade was just closed trade.close_date = order_obj.order_filled_date Trade.commit() - continue + break + else: + trade.exit_reason = prev_exit_reason except ExchangeError: logger.warning("Error finding onexchange order") From 0c22710ddd7e67dae0c3a11b7bf9e201ef17b71c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 19:30:24 +0200 Subject: [PATCH 09/53] Add API endpoint to force trade reloading --- freqtrade/rpc/api_server/api_v1.py | 11 +++++++++-- freqtrade/rpc/rpc.py | 12 ++++++++++++ tests/rpc/test_rpc_apiserver.py | 27 +++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 5ee5e36c4..6642f5827 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -44,7 +44,8 @@ logger = logging.getLogger(__name__) # 2.24: Add cancel_open_order endpoint # 2.25: Add several profit values to /status endpoint # 2.26: increase /balance output -API_VERSION = 2.26 +# 2.27: Add /trades//reload endpoint +API_VERSION = 2.27 # Public API, requires no auth. router_public = APIRouter() @@ -127,11 +128,17 @@ def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)): @router.delete('/trades/{tradeid}/open-order', response_model=OpenTradeSchema, tags=['trading']) -def cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)): +def trade_cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)): rpc._rpc_cancel_open_order(tradeid) return rpc._rpc_trade_status([tradeid])[0] +@router.get('/trades/{tradeid}/reload', response_model=OpenTradeSchema, tags=['trading']) +def trade_reload(tradeid: int, rpc: RPC = Depends(get_rpc)): + rpc._rpc_reload_trade_from_exchange(tradeid) + return rpc._rpc_trade_status([tradeid])[0] + + # TODO: Missing response model @router.get('/edge', tags=['info']) def edge(rpc: RPC = Depends(get_rpc)): diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 35e08cbc0..a5f6a0a66 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -741,6 +741,18 @@ class RPC: return {'status': 'No more entries will occur from now. Run /reload_config to reset.'} + def _rpc_reload_trade_from_exchange(self, trade_id: str) -> Dict[str, str]: + """ + Handler for reload_trade_from_exchange. + Reloads a trade from it's orders, should manual interaction have happened. + """ + trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first() + if not trade: + raise RPCException(f"Could not find trade with id {trade_id}.") + + self._freqtrade.handle_onexchange_order(trade) + return {'status': 'Reloaded from orders from exchange'} + def __exec_force_exit(self, trade: Trade, ordertype: Optional[str], amount: Optional[float] = None) -> None: # Check if there is there is an open order diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8123e4689..51fddbb88 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -740,6 +740,33 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short assert cancel_mock.call_count == 1 +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_trade_reload_trade(botclient, mocker, fee, markets, ticker, is_short): + ftbot, client = botclient + patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) + stoploss_mock = MagicMock() + cancel_mock = MagicMock() + ftbot.handle_onexchange_order = MagicMock() + mocker.patch.multiple( + EXMS, + markets=PropertyMock(return_value=markets), + fetch_ticker=ticker, + cancel_order=cancel_mock, + cancel_stoploss_order=stoploss_mock, + ) + + rc = client_get(client, f"{BASE_URI}/trades/10/reload") + assert_response(rc, 502) + assert 'Could not find trade with id 10.' in rc.json()['error'] + assert ftbot.handle_onexchange_order.call_count == 0 + + create_mock_trades(fee, is_short=is_short) + Trade.commit() + + rc = client_get(client, f"{BASE_URI}/trades/5/reload") + assert ftbot.handle_onexchange_order.call_count == 1 + + def test_api_logs(botclient): ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/logs") From 7287e9da1dce45fd839df640e3f918b194e769d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 19:34:37 +0200 Subject: [PATCH 10/53] Add telegram endpoint for reload_trade --- freqtrade/rpc/telegram.py | 12 ++++++++++++ tests/rpc/test_rpc_telegram.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 6e509950c..0779e5795 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -196,6 +196,7 @@ class Telegram(RPCHandler): self._force_enter, order_side=SignalDirection.LONG)), CommandHandler('forceshort', partial( self._force_enter, order_side=SignalDirection.SHORT)), + CommandHandler('reload_trade', self._reload_trade_from_exchange), CommandHandler('trades', self._trades), CommandHandler('delete', self._delete_trade), CommandHandler(['coo', 'cancel_open_order'], self._cancel_open_order), @@ -1074,6 +1075,17 @@ class Telegram(RPCHandler): msg = self._rpc._rpc_stopentry() await self._send_msg(f"Status: `{msg['status']}`") + @authorized_only + async def _reload_trade_from_exchange(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /reload_trade . + """ + if not context.args or len(context.args) == 0: + raise RPCException("Trade-id not set.") + trade_id = context.args[0] + msg = self._rpc._rpc_reload_trade_from_exchange(trade_id) + await self._send_msg(f"Status: `{msg['status']}`") + @authorized_only async def _force_exit(self, update: Update, context: CallbackContext) -> None: """ diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 4b4c2b028..8570b2ad5 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1760,6 +1760,25 @@ async def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0] +@pytest.mark.parametrize('is_short', [True, False]) +async def test_telegram_reload_trade_from_exchange(mocker, update, default_conf, fee, is_short): + + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + context = MagicMock() + context.args = [] + + await telegram._reload_trade_from_exchange(update=update, context=context) + assert "Trade-id not set." in msg_mock.call_args_list[0][0][0] + + msg_mock.reset_mock() + create_mock_trades(fee, is_short=is_short) + + context.args = [5] + + await telegram._reload_trade_from_exchange(update=update, context=context) + assert "Status: `Reloaded from orders from exchange`" in msg_mock.call_args_list[0][0][0] + + @pytest.mark.parametrize('is_short', [True, False]) async def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker): From 25bed7bb8700207d52be30b2632482b49bf0c098 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 19:39:52 +0200 Subject: [PATCH 11/53] Update telegram help with reload_trade --- freqtrade/rpc/telegram.py | 1 + tests/rpc/test_rpc_telegram.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0779e5795..7eb3028ee 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1573,6 +1573,7 @@ class Telegram(RPCHandler): "*/fx |all:* `Alias to /forceexit`\n" f"{force_enter_text if self._config.get('force_entry_enable', False) else ''}" "*/delete :* `Instantly delete the given trade in the database`\n" + "*/reload_trade :* `Relade trade from exchange Orders`\n" "*/cancel_open_order :* `Cancels open orders for trade. " "Only valid when the trade has open orders.`\n" "*/coo |all:* `Alias to /cancel_open_order`\n" diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 8570b2ad5..c5bdb5e5b 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -143,8 +143,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " "['balance'], ['start'], ['stop'], " "['forceexit', 'forcesell', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], " - "['trades'], ['delete'], ['cancel_open_order', 'coo'], ['performance'], " - "['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], " + "['reload_trade'], ['trades'], ['delete'], ['cancel_open_order', 'coo'], " + "['performance'], ['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], " "['stats'], ['daily'], ['weekly'], ['monthly'], " "['count'], ['locks'], ['delete_locks', 'unlock'], " "['reload_conf', 'reload_config'], ['show_conf', 'show_config'], " From d0b5c7d2168a49fe6f9b7c093aa9870398f1c37e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 19:40:05 +0200 Subject: [PATCH 12/53] update telegram/api documentation with new endpoint --- docs/rest-api.md | 4 +++- docs/telegram-usage.md | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/rest-api.md b/docs/rest-api.md index 860a44499..5b33bfa6f 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -134,7 +134,9 @@ python3 scripts/rest_client.py --config rest_config.json [optional par | `reload_config` | Reloads the configuration file. | `trades` | List last trades. Limited to 500 trades per call. | `trade/` | Get specific trade. -| `delete_trade ` | Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `trade/` | DELETE - Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `trade//open-order` | DELETE - Cancel open order for this trade. +| `trade//reload` | GET - Reload a trade from the Exchange. Only works in live, and can potentially help recover a trade that was manually sold on the exchange. | `show_config` | Shows part of the current configuration with relevant settings to operation. | `logs` | Shows last log messages. | `status` | Lists all open trades. diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index e6017e271..1b36c60ad 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -187,6 +187,7 @@ official commands. You can ask at any moment for help with `/help`. | `/forcelong [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`force_entry_enable` must be set to True) | `/forceshort [rate]` | Instantly shorts the given pair. Rate is optional and only applies to limit orders. This will only work on non-spot markets. (`force_entry_enable` must be set to True) | `/delete ` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `/reload_trade ` | Reload a trade from the Exchange. Only works in live, and can potentially help recover a trade that was manually sold on the exchange. | `/cancel_open_order | /coo ` | Cancel an open order for a trade. | **Metrics** | | `/profit []` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default) From b0b036c457f0cc62e0e98ced1df19d68633d4761 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Apr 2023 06:45:09 +0200 Subject: [PATCH 13/53] Fix logic lapsus in check_exit_amount --- freqtrade/freqtradebot.py | 3 +++ freqtrade/wallets.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8b877541c..59f764111 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -463,6 +463,7 @@ class FreqtradeBot(LoggingMixin): if trade_order: continue logger.info(f"Found previously unknown order {order['id']} for {trade.pair}.") + order_obj = Order.parse_from_ccxt_object(order, trade.pair, order['side']) order_obj.order_filled_date = datetime.fromtimestamp( safe_value_fallback(order, 'lastTradeTimestamp', 'timestamp') // 1000, @@ -473,6 +474,7 @@ class FreqtradeBot(LoggingMixin): prev_exit_reason = trade.exit_reason trade.exit_reason = ExitType.SOLD_ON_EXCHANGE.value self.update_trade_state(trade, order['id'], order) + logger.info(f"handled order {order['id']}") if not trade.is_open: # Trade was just closed @@ -481,6 +483,7 @@ class FreqtradeBot(LoggingMixin): break else: trade.exit_reason = prev_exit_reason + Trade.commit() except ExchangeError: logger.warning("Error finding onexchange order") diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index ecac638c6..9a33d1fb1 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -184,7 +184,7 @@ class Wallets: def _check_exit_amount(self, trade: Trade) -> bool: if trade.trading_mode != TradingMode.FUTURES: # Slightly higher offset than in safe_exit_amount. - wallet_amount: float = self.get_total(trade.safe_base_currency) * 0.981 + wallet_amount: float = self.get_total(trade.safe_base_currency) * (2 - 0.981) else: # wallet_amount: float = self.wallets.get_free(trade.safe_base_currency) position = self._positions.get(trade.pair) From d29a425baa81e050e20844e7090460bf8f2fafca Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Apr 2023 07:03:28 +0200 Subject: [PATCH 14/53] Update parameter type in RPC modules --- freqtrade/rpc/rpc.py | 2 +- freqtrade/rpc/telegram.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index a5f6a0a66..9064c8a58 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -741,7 +741,7 @@ class RPC: return {'status': 'No more entries will occur from now. Run /reload_config to reset.'} - def _rpc_reload_trade_from_exchange(self, trade_id: str) -> Dict[str, str]: + def _rpc_reload_trade_from_exchange(self, trade_id: int) -> Dict[str, str]: """ Handler for reload_trade_from_exchange. Reloads a trade from it's orders, should manual interaction have happened. diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7eb3028ee..b25aa3e32 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1082,7 +1082,7 @@ class Telegram(RPCHandler): """ if not context.args or len(context.args) == 0: raise RPCException("Trade-id not set.") - trade_id = context.args[0] + trade_id = int(context.args[0]) msg = self._rpc._rpc_reload_trade_from_exchange(trade_id) await self._send_msg(f"Status: `{msg['status']}`") From e88e259033a45b555f00fccfaf8382e86dafd75c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Apr 2023 07:09:26 +0200 Subject: [PATCH 15/53] explicitly test check_exit_amount --- tests/test_wallets.py | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 7ccc8d0f5..09adf6e15 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -3,9 +3,11 @@ from copy import deepcopy from unittest.mock import MagicMock import pytest +from sqlalchemy import select from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.exceptions import DependencyException +from freqtrade.persistence import Trade from tests.conftest import EXMS, create_mock_trades, get_patched_freqtradebot, patch_wallet @@ -364,3 +366,48 @@ def test_sync_wallet_futures_dry(mocker, default_conf, fee): free = freqtrade.wallets.get_free('BTC') used = freqtrade.wallets.get_used('BTC') assert free + used == total + + +def test_check_exit_amount(mocker, default_conf, fee): + freqtrade = get_patched_freqtradebot(mocker, default_conf) + update_mock = mocker.patch("freqtrade.wallets.Wallets.update") + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) + + create_mock_trades(fee, is_short=None) + trade = Trade.session.scalars(select(Trade)).first() + assert trade.amount == 123 + + assert freqtrade.wallets.check_exit_amount(trade) is True + assert update_mock.call_count == 0 + assert total_mock.call_count == 1 + + update_mock.reset_mock() + # Reduce returned amount to below the trade amount - which should + # trigger a wallet update and return False, triggering "order refinding" + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=100) + assert freqtrade.wallets.check_exit_amount(trade) is False + assert update_mock.call_count == 1 + assert total_mock.call_count == 2 + + +def test_check_exit_amount_futures(mocker, default_conf, fee): + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + freqtrade = get_patched_freqtradebot(mocker, default_conf) + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) + + create_mock_trades(fee, is_short=None) + trade = Trade.session.scalars(select(Trade)).first() + trade.trading_mode = 'futures' + assert trade.amount == 123 + + assert freqtrade.wallets.check_exit_amount(trade) is True + assert total_mock.call_count == 0 + + update_mock = mocker.patch("freqtrade.wallets.Wallets.update") + trade.amount = 150 + # Reduce returned amount to below the trade amount - which should + # trigger a wallet update and return False, triggering "order refinding" + assert freqtrade.wallets.check_exit_amount(trade) is False + assert total_mock.call_count == 0 + assert update_mock.call_count == 1 From 491d2cb024066f38b4d25df9f2e1d4ad554f5489 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Apr 2023 20:32:51 +0200 Subject: [PATCH 16/53] Explicit test for handle_onexchange_order --- tests/test_freqtradebot.py | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index ea99061b8..8aa3f63d5 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5552,6 +5552,51 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap assert log_has(f"Error updating {order['id']}.", caplog) +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_onexchange_order(mocker, default_conf_usdt, limit_order, is_short, caplog): + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + mock_uts = mocker.spy(freqtrade, 'update_trade_state') + + entry_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + mock_fo = mocker.patch(f'{EXMS}.fetch_orders', return_value=[ + entry_order, + exit_order, + ]) + + order_id = entry_order['id'] + + trade = Trade( + open_order_id=order_id, + pair='ETH/USDT', + fee_open=0.001, + fee_close=0.001, + open_rate=entry_order['price'], + open_date=arrow.utcnow().datetime, + stake_amount=entry_order['cost'], + amount=entry_order['amount'], + exchange="binance", + is_short=is_short, + leverage=1, + ) + + trade.orders.append(Order.parse_from_ccxt_object( + entry_order, 'ADA/USDT', entry_side(is_short)) + ) + Trade.session.add(trade) + freqtrade.handle_onexchange_order(trade) + assert log_has_re(r"Found previously unknown order .*", caplog) + assert mock_uts.call_count == 1 + assert mock_fo.call_count == 1 + + trade = Trade.session.scalars(select(Trade)).first() + + assert len(trade.orders) == 2 + assert trade.is_open is False + assert trade.exit_reason == ExitType.SOLD_ON_EXCHANGE.value + + def test_get_valid_price(mocker, default_conf_usdt) -> None: patch_RPCManager(mocker) patch_exchange(mocker) From 395ac5f6dc4289a7a9cf60751759b547b15eec47 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 27 Apr 2023 06:23:34 +0200 Subject: [PATCH 17/53] Update integration test --- tests/test_integration.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 9fb9fd8b3..2949f1ef2 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -75,8 +75,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, _notify_exit=MagicMock(), ) mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock) - wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock()) - mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1000)) + wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update") + mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=1000) + mocker.patch("freqtrade.wallets.Wallets.check_exit_amount", return_value=True) freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade.strategy.order_types['stoploss_on_exchange'] = True From a74a081e61e34b8fc38ff9cc3ac15e5cbec0cf8a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 06:34:05 +0200 Subject: [PATCH 18/53] Check for repository changes --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e7b11672..e0f59fbda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,18 @@ jobs: # Allow failure for coveralls coveralls || true + - name: Check for repository changes + run: | + git status + if [ -n "$(git status --porcelain)" ]; then + echo "Repository is dirty, changes detected:" + git status + git diff + exit 1 + else + echo "Repository is clean, no changes detected." + fi + - name: Backtesting (multi) run: | cp config_examples/config_bittrex.example.json config.json From 7e023419de4821a252c253a3a36f3db7d85e3e7e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 07:05:43 +0200 Subject: [PATCH 19/53] Auto-mock user_dir to tmpdir This will avoid depending on the user directory being present for tests --- tests/conftest.py | 6 ++++++ tests/test_configuration.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1c737b3aa..b32d0a8bc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -411,6 +411,12 @@ def patch_gc(mocker) -> None: mocker.patch("freqtrade.main.gc_set_threshold") +@pytest.fixture(autouse=True) +def patched_user_dir(mocker, tmpdir) -> None: + mocker.patch('freqtrade.configuration.configuration.create_userdata_dir', + return_value=Path(tmpdir) / "user_data") + + @pytest.fixture(autouse=True) def patch_coingekko(mocker) -> None: """ diff --git a/tests/test_configuration.py b/tests/test_configuration.py index c445b989d..5b09abbd3 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1271,7 +1271,7 @@ def test_pairlist_resolving_with_config_pl_not_exists(mocker, default_conf): configuration.get_config() -def test_pairlist_resolving_fallback(mocker): +def test_pairlist_resolving_fallback(mocker, tmpdir): mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock())) mocker.patch("freqtrade.configuration.configuration.load_file", @@ -1290,7 +1290,7 @@ def test_pairlist_resolving_fallback(mocker): assert config['pairs'] == ['ETH/BTC', 'XRP/BTC'] assert config['exchange']['name'] == 'binance' - assert config['datadir'] == Path.cwd() / "user_data/data/binance" + assert config['datadir'] == Path(tmpdir) / "user_data/data/binance" @pytest.mark.parametrize("setting", [ From c60c4b9abb158e0508b9b0bc70f12ce4c94f3f9f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 07:10:34 +0200 Subject: [PATCH 20/53] Update user_dir fixture to return user_data path --- tests/conftest.py | 6 ++++-- tests/test_strategy_updater.py | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b32d0a8bc..4b725e2db 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -412,9 +412,11 @@ def patch_gc(mocker) -> None: @pytest.fixture(autouse=True) -def patched_user_dir(mocker, tmpdir) -> None: +def user_dir(mocker, tmpdir) -> Path: + user_dir = Path(tmpdir) / "user_data" mocker.patch('freqtrade.configuration.configuration.create_userdata_dir', - return_value=Path(tmpdir) / "user_data") + return_value=user_dir) + return user_dir @pytest.fixture(autouse=True) diff --git a/tests/test_strategy_updater.py b/tests/test_strategy_updater.py index 597d49fda..3b48c952c 100644 --- a/tests/test_strategy_updater.py +++ b/tests/test_strategy_updater.py @@ -16,18 +16,18 @@ if sys.version_info < (3, 9): pytest.skip("StrategyUpdater is not compatible with Python 3.8", allow_module_level=True) -def test_strategy_updater_start(tmpdir, capsys) -> None: +def test_strategy_updater_start(user_dir, capsys) -> None: # Effective test without mocks. teststrats = Path(__file__).parent / 'strategy/strats' - tmpdirp = Path(tmpdir) / "strategies" - tmpdirp.mkdir() + tmpdirp = Path(user_dir) / "strategies" + tmpdirp.mkdir(parents=True, exist_ok=True) shutil.copy(teststrats / 'strategy_test_v2.py', tmpdirp) old_code = (teststrats / 'strategy_test_v2.py').read_text() args = [ "strategy-updater", "--userdir", - str(tmpdir), + str(user_dir), "--strategy-list", "StrategyTestV2" ] @@ -36,9 +36,9 @@ def test_strategy_updater_start(tmpdir, capsys) -> None: start_strategy_update(pargs) - assert Path(tmpdir / "strategies_orig_updater").exists() + assert Path(user_dir / "strategies_orig_updater").exists() # Backup file exists - assert Path(tmpdir / "strategies_orig_updater" / 'strategy_test_v2.py').exists() + assert Path(user_dir / "strategies_orig_updater" / 'strategy_test_v2.py').exists() # updated file exists new_file = Path(tmpdirp / 'strategy_test_v2.py') assert new_file.exists() From 3ba1eb6baa56d3ec83f07f0e919820be7fbe9f93 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 10:45:17 +0200 Subject: [PATCH 21/53] Improve concurrency group --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0f59fbda..978341f6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ on: - cron: '0 5 * * 4' concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}${{ github.head_ref }} cancel-in-progress: true permissions: repository-projects: read From 800c6223ed0da7d7b416ca5253d354a6d3146d26 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 10:45:30 +0200 Subject: [PATCH 22/53] Quote concurrency group --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 978341f6e..e8d2f3312 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ on: - cron: '0 5 * * 4' concurrency: - group: ${{ github.workflow }}-${{ github.ref }}${{ github.head_ref }} + group: "${{ github.workflow }}-${{ github.ref }}${{ github.head_ref }}"" cancel-in-progress: true permissions: repository-projects: read From 1ec1abdc333a097c20c8b8da792c0022ab04cff6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 10:45:52 +0200 Subject: [PATCH 23/53] Fix syntax --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8d2f3312..5f07c4a75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ on: - cron: '0 5 * * 4' concurrency: - group: "${{ github.workflow }}-${{ github.ref }}${{ github.head_ref }}"" + group: "${{ github.workflow }}-${{ github.ref }}${{ github.head_ref }}" cancel-in-progress: true permissions: repository-projects: read From bd6d4d5d2d963e6c8159d48dd1e10c7bed1aa1da Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 10:50:09 +0200 Subject: [PATCH 24/53] Event-name for concurrency group? --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f07c4a75..2abe8823e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ on: - cron: '0 5 * * 4' concurrency: - group: "${{ github.workflow }}-${{ github.ref }}${{ github.head_ref }}" + group: "${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}" cancel-in-progress: true permissions: repository-projects: read From 2ecd63234d3e921fe9ca3c36a06a0c4cb2ab552f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 10:54:46 +0200 Subject: [PATCH 25/53] Remove git status again --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2abe8823e..3778a08a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,6 @@ jobs: - name: Check for repository changes run: | - git status if [ -n "$(git status --porcelain)" ]; then echo "Repository is dirty, changes detected:" git status From 395bf49198ced7fdfe4a4bd7f2ab7739c6dbfde2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 10:55:29 +0200 Subject: [PATCH 26/53] Run Repo-check for macOS, too --- .github/workflows/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3778a08a4..c3a7e8a7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -185,6 +185,17 @@ jobs: run: | pytest --random-order + - name: Check for repository changes + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "Repository is dirty, changes detected:" + git status + git diff + exit 1 + else + echo "Repository is clean, no changes detected." + fi + - name: Backtesting run: | cp config_examples/config_bittrex.example.json config.json From 963ff8c62006c84a63b45e3a22d06acec40c0929 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 10:57:24 +0200 Subject: [PATCH 27/53] Run Repo check on windows, too. --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3a7e8a7e..bd9bf02fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -259,6 +259,18 @@ jobs: run: | pytest --random-order + - name: Check for repository changes + run: | + if (git status --porcelain) { + Write-Host "Repository is dirty, changes detected:" + git status + git diff + exit 1 + } + else { + Write-Host "Repository is clean, no changes detected." + } + - name: Backtesting run: | cp config_examples/config_bittrex.example.json config.json From 1c1005247e3ff88c07eae8a2596419983ae9fa2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 20:09:24 +0200 Subject: [PATCH 28/53] Don't hardcode user_data in tests --- tests/test_plotting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 9f04ba20a..e43009aee 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -282,13 +282,13 @@ def test_generate_Plot_filename(): assert fn == "freqtrade-plot-UNITTEST_BTC-5m.html" -def test_generate_plot_file(mocker, caplog): +def test_generate_plot_file(mocker, caplog, user_dir): fig = generate_empty_figure() plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock()) store_plot_file(fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html", - directory=Path("user_data/plot")) + directory=user_dir / "plot") - expected_fn = str(Path("user_data/plot/freqtrade-plot-UNITTEST_BTC-5m.html")) + expected_fn = str(user_dir / "plot/freqtrade-plot-UNITTEST_BTC-5m.html") assert plot_mock.call_count == 1 assert plot_mock.call_args[0][0] == fig assert (plot_mock.call_args_list[0][1]['filename'] From f7179f7c93be346f4f2910b652d13db39018f78c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 20:30:24 +0200 Subject: [PATCH 29/53] Fix last test with dependency on local user_data dir --- tests/data/test_entryexitanalysis.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/data/test_entryexitanalysis.py b/tests/data/test_entryexitanalysis.py index 367ad8394..810e2c53b 100644 --- a/tests/data/test_entryexitanalysis.py +++ b/tests/data/test_entryexitanalysis.py @@ -18,8 +18,9 @@ def entryexitanalysis_cleanup() -> None: Backtesting.cleanup() -def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmpdir, capsys): +def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, user_dir, capsys): caplog.set_level(logging.INFO) + (user_dir / 'backtest_results').mkdir(parents=True, exist_ok=True) default_conf.update({ "use_exit_signal": True, @@ -80,7 +81,7 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp 'backtesting', '--config', 'config.json', '--datadir', str(testdatadir), - '--user-data-dir', str(tmpdir), + '--user-data-dir', str(user_dir), '--timeframe', '5m', '--timerange', '1515560100-1517287800', '--export', 'signals', @@ -98,7 +99,7 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp 'backtesting-analysis', '--config', 'config.json', '--datadir', str(testdatadir), - '--user-data-dir', str(tmpdir), + '--user-data-dir', str(user_dir), ] # test group 0 and indicator list From b970ddeb660d8ef62b4c0a18dc20e3d5c4072488 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 20:44:41 +0200 Subject: [PATCH 30/53] Fix unused import --- tests/test_plotting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index e43009aee..377caf59c 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -1,5 +1,4 @@ from copy import deepcopy -from pathlib import Path from unittest.mock import MagicMock import pandas as pd From 1d36878938b78e308394bb09a59e4a02945df575 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 20:50:52 +0200 Subject: [PATCH 31/53] Bump min-requirements for python-telegram bot --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0421e307a..7896d4dd7 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ setup( # from requirements.txt 'ccxt>=2.6.26', 'SQLAlchemy>=2.0.6', - 'python-telegram-bot>=13.4', + 'python-telegram-bot>=20.1', 'arrow>=0.17.0', 'cachetools', 'requests', From 6000e68420810ac239faf2b243587dd08d740da7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 May 2023 20:51:33 +0200 Subject: [PATCH 32/53] bump ccxt min dependency --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7896d4dd7..b59e98ae8 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=2.6.26', + 'ccxt>=3.0.0', 'SQLAlchemy>=2.0.6', 'python-telegram-bot>=20.1', 'arrow>=0.17.0', From 31d15da49e4cd524bf45eb6e5f842b0e06054820 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 12 May 2023 08:16:48 +0000 Subject: [PATCH 33/53] add disclaimers everywhere about how example strategies are meant as examples --- docs/freqai-reinforcement-learning.md | 11 ++++++++++- docs/freqai.md | 5 ++++- freqtrade/freqai/RL/BaseEnvironment.py | 6 ++++++ .../freqai/RL/BaseReinforcementLearningModel.py | 6 ++++++ .../prediction_models/ReinforcementLearner.py | 6 ++++++ freqtrade/templates/FreqaiExampleStrategy.py | 13 ++++++++----- .../test_models/ReinforcementLearner_test_3ac.py | 5 +++++ .../test_models/ReinforcementLearner_test_4ac.py | 5 +++++ 8 files changed, 50 insertions(+), 7 deletions(-) diff --git a/docs/freqai-reinforcement-learning.md b/docs/freqai-reinforcement-learning.md index 962827348..28672177f 100644 --- a/docs/freqai-reinforcement-learning.md +++ b/docs/freqai-reinforcement-learning.md @@ -135,7 +135,11 @@ Parameter details can be found [here](freqai-parameter-table.md), but in general ## Creating a custom reward function -As you begin to modify the strategy and the prediction model, you will quickly realize some important differences between the Reinforcement Learner and the Regressors/Classifiers. Firstly, the strategy does not set a target value (no labels!). Instead, you set the `calculate_reward()` function inside the `MyRLEnv` class (see below). A default `calculate_reward()` is provided inside `prediction_models/ReinforcementLearner.py` to demonstrate the necessary building blocks for creating rewards, but users are encouraged to create their own custom reinforcement learning model class (see below) and save it to `user_data/freqaimodels`. It is inside the `calculate_reward()` where creative theories about the market can be expressed. For example, you can reward your agent when it makes a winning trade, and penalize the agent when it makes a losing trade. Or perhaps, you wish to reward the agent for entering trades, and penalize the agent for sitting in trades too long. Below we show examples of how these rewards are all calculated: +!!! danger "Not for production" + Warning! + The reward function provided with the Freqtrade source code is a showcase of functionality designed to show/test as many possible environment control features as possible. It is also designed to run quickly on small computers. This is a benchmark, it is *not* for live production. Please beware that you will need to create your own custom_reward() function or use a template built by other users outside of the Freqtrade source code. + +As you begin to modify the strategy and the prediction model, you will quickly realize some important differences between the Reinforcement Learner and the Regressors/Classifiers. Firstly, the strategy does not set a target value (no labels!). Instead, you set the `calculate_reward()` function inside the `MyRLEnv` class (see below). A default `calculate_reward()` is provided inside `prediction_models/ReinforcementLearner.py` to demonstrate the necessary building blocks for creating rewards, but this is *not* designed for production. Users *must* create their own custom reinforcement learning model class or use a pre-built one from outside the Freqtrade source code and save it to `user_data/freqaimodels`. It is inside the `calculate_reward()` where creative theories about the market can be expressed. For example, you can reward your agent when it makes a winning trade, and penalize the agent when it makes a losing trade. Or perhaps, you wish to reward the agent for entering trades, and penalize the agent for sitting in trades too long. Below we show examples of how these rewards are all calculated: ```python from freqtrade.freqai.prediction_models.ReinforcementLearner import ReinforcementLearner @@ -169,6 +173,11 @@ As you begin to modify the strategy and the prediction model, you will quickly r User made custom environment. This class inherits from BaseEnvironment and gym.env. Users can override any functions from those parent classes. Here is an example of a user customized `calculate_reward()` function. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. """ def calculate_reward(self, action: int) -> float: # first, penalize if the action is not valid diff --git a/docs/freqai.md b/docs/freqai.md index ef8efb840..b3cd8cd07 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -32,7 +32,10 @@ The easiest way to quickly test FreqAI is to run it in dry mode with the followi freqtrade trade --config config_examples/config_freqai.example.json --strategy FreqaiExampleStrategy --freqaimodel LightGBMRegressor --strategy-path freqtrade/templates ``` -You will see the boot-up process of automatic data downloading, followed by simultaneous training and trading. +You will see the boot-up process of automatic data downloading, followed by simultaneous training and trading. + +!!! danger "Not for production" + The example strategy provided with the Freqtrade source code is designed for showcasing/testing a wide variety of FreqAI features. It is also designed to run on small computers so that it can be used as a benchmark between developers and users. It is *not* designed to be run in production. An example strategy, prediction model, and config to use as a starting points can be found in `freqtrade/templates/FreqaiExampleStrategy.py`, `freqtrade/freqai/prediction_models/LightGBMRegressor.py`, and diff --git a/freqtrade/freqai/RL/BaseEnvironment.py b/freqtrade/freqai/RL/BaseEnvironment.py index 08bb93347..7c83a7e42 100644 --- a/freqtrade/freqai/RL/BaseEnvironment.py +++ b/freqtrade/freqai/RL/BaseEnvironment.py @@ -306,6 +306,12 @@ class BaseEnvironment(gym.Env): """ An example reward function. This is the one function that users will likely wish to inject their own creativity into. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. + :param action: int = The action made by the agent for the current candle. :return: float = the reward to give to the agent for current step (used for optimization diff --git a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py index e2c0f5fda..3c6f2c142 100644 --- a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py +++ b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py @@ -371,6 +371,12 @@ class BaseReinforcementLearningModel(IFreqaiModel): """ An example reward function. This is the one function that users will likely wish to inject their own creativity into. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. + :param action: int = The action made by the agent for the current candle. :return: float = the reward to give to the agent for current step (used for optimization diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner.py b/freqtrade/freqai/prediction_models/ReinforcementLearner.py index a5c2e12b5..8c9d9bdef 100644 --- a/freqtrade/freqai/prediction_models/ReinforcementLearner.py +++ b/freqtrade/freqai/prediction_models/ReinforcementLearner.py @@ -97,6 +97,12 @@ class ReinforcementLearner(BaseReinforcementLearningModel): """ An example reward function. This is the one function that users will likely wish to inject their own creativity into. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. + :param action: int = The action made by the agent for the current candle. :return: float = the reward to give to the agent for current step (used for optimization diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index 493ea17f3..347efdda0 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -15,12 +15,15 @@ logger = logging.getLogger(__name__) class FreqaiExampleStrategy(IStrategy): """ Example strategy showing how the user connects their own - IFreqaiModel to the strategy. Namely, the user uses: - self.freqai.start(dataframe, metadata) + IFreqaiModel to the strategy. - to make predictions on their data. feature_engineering_*() automatically - generate the variety of features indicated by the user in the - canonical freqtrade configuration file under config['freqai']. + Warning! This is a showcase of functionality, + which means that it is designed to show various functions of FreqAI + and it runs on all computers. We use this showcase to help users + understand how to build a strategy, and we use it as a benchmark + to help debug possible problems. + + This means this is *not* meant to be run live in production. """ minimal_roi = {"0": 0.1, "240": -1} diff --git a/tests/freqai/test_models/ReinforcementLearner_test_3ac.py b/tests/freqai/test_models/ReinforcementLearner_test_3ac.py index c267c76a8..f77120c3c 100644 --- a/tests/freqai/test_models/ReinforcementLearner_test_3ac.py +++ b/tests/freqai/test_models/ReinforcementLearner_test_3ac.py @@ -18,6 +18,11 @@ class ReinforcementLearner_test_3ac(ReinforcementLearner): """ User can override any function in BaseRLEnv and gym.Env. Here the user sets a custom reward based on profit and trade duration. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. """ def calculate_reward(self, action: int) -> float: diff --git a/tests/freqai/test_models/ReinforcementLearner_test_4ac.py b/tests/freqai/test_models/ReinforcementLearner_test_4ac.py index 29e3e3b64..4fc2b0005 100644 --- a/tests/freqai/test_models/ReinforcementLearner_test_4ac.py +++ b/tests/freqai/test_models/ReinforcementLearner_test_4ac.py @@ -18,6 +18,11 @@ class ReinforcementLearner_test_4ac(ReinforcementLearner): """ User can override any function in BaseRLEnv and gym.Env. Here the user sets a custom reward based on profit and trade duration. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. """ def calculate_reward(self, action: int) -> float: From db0645ed1b296f514cf7bfcd12f1065d64794ac3 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 12 May 2023 08:32:52 +0000 Subject: [PATCH 34/53] add helpful hints for reward creation --- docs/freqai-reinforcement-learning.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/freqai-reinforcement-learning.md b/docs/freqai-reinforcement-learning.md index 28672177f..547bb13eb 100644 --- a/docs/freqai-reinforcement-learning.md +++ b/docs/freqai-reinforcement-learning.md @@ -141,6 +141,9 @@ Parameter details can be found [here](freqai-parameter-table.md), but in general As you begin to modify the strategy and the prediction model, you will quickly realize some important differences between the Reinforcement Learner and the Regressors/Classifiers. Firstly, the strategy does not set a target value (no labels!). Instead, you set the `calculate_reward()` function inside the `MyRLEnv` class (see below). A default `calculate_reward()` is provided inside `prediction_models/ReinforcementLearner.py` to demonstrate the necessary building blocks for creating rewards, but this is *not* designed for production. Users *must* create their own custom reinforcement learning model class or use a pre-built one from outside the Freqtrade source code and save it to `user_data/freqaimodels`. It is inside the `calculate_reward()` where creative theories about the market can be expressed. For example, you can reward your agent when it makes a winning trade, and penalize the agent when it makes a losing trade. Or perhaps, you wish to reward the agent for entering trades, and penalize the agent for sitting in trades too long. Below we show examples of how these rewards are all calculated: +!!! note "Hint" + The best reward functions are ones that are continuously differentiable, and well scaled. In other words, adding a single large negative penalty to a rare event is not a good idea, and the neural net will not be able to learn that function. Instead, it is better to add a small negative penalty to a common event. This will help the agent learn faster. Not only this, but you can help improve the continuity of your rewards/penalties by having them scale with severity according to some linear/exponential functions. In other words, you'd slowly scale the penalty as the duration of the trade increases. This is better than a single large penalty occuring at a single point in time. + ```python from freqtrade.freqai.prediction_models.ReinforcementLearner import ReinforcementLearner from freqtrade.freqai.RL.Base5ActionRLEnv import Actions, Base5ActionRLEnv, Positions From b2a3fe68795da4a15dd670c21439a8632483bf39 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 08:09:00 +0200 Subject: [PATCH 35/53] Improve remove credentials --- freqtrade/exchange/common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 42a7094ba..8d7bc37b6 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -4,6 +4,7 @@ import time from functools import wraps from typing import Any, Callable, Optional, TypeVar, cast, overload +from freqtrade.constants import Config from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError from freqtrade.mixins import LoggingMixin @@ -87,7 +88,7 @@ EXCHANGE_HAS_OPTIONAL = [ ] -def remove_credentials(config) -> None: +def remove_credentials(config: Config) -> None: """ Removes exchange keys from the configuration and specifies dry-run Used for backtesting / hyperopt / edge and utils. @@ -95,6 +96,7 @@ def remove_credentials(config) -> None: """ if config.get('dry_run', False): config['exchange']['key'] = '' + config['exchange']['apiKey'] = '' config['exchange']['secret'] = '' config['exchange']['password'] = '' config['exchange']['uid'] = '' From 1552d81f45147f08332a398dab8fa2547f059a75 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 08:27:27 +0200 Subject: [PATCH 36/53] Simplify load_exchange interface --- freqtrade/commands/data_commands.py | 4 +-- freqtrade/commands/list_commands.py | 4 +-- freqtrade/commands/pairlist_commands.py | 2 +- freqtrade/freqtradebot.py | 2 +- freqtrade/optimize/backtesting.py | 3 +-- freqtrade/optimize/edge_cli.py | 2 +- freqtrade/plot/plotting.py | 4 +-- freqtrade/resolvers/exchange_resolver.py | 3 ++- freqtrade/rpc/api_server/deps.py | 2 +- tests/conftest.py | 2 +- tests/exchange/test_ccxt_compat.py | 2 +- tests/exchange/test_exchange.py | 32 ++++++++++++++---------- 12 files changed, 34 insertions(+), 28 deletions(-) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index bcef1c252..ed1571002 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -52,7 +52,7 @@ def start_download_data(args: Dict[str, Any]) -> None: pairs_not_available: List[str] = [] # Init exchange - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) markets = [p for p, m in exchange.markets.items() if market_is_active(m) or config.get('include_inactive')] @@ -125,7 +125,7 @@ def start_convert_trades(args: Dict[str, Any]) -> None: "Please check the documentation on how to configure this.") # Init exchange - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) # Manual validations of relevant settings if not config['exchange'].get('skip_pair_validation', False): exchange.validate_pairs(config['pairs']) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 4e0623081..3358f8cc8 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -114,7 +114,7 @@ def start_list_timeframes(args: Dict[str, Any]) -> None: config['timeframe'] = None # Init exchange - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) if args['print_one_column']: print('\n'.join(exchange.timeframes)) @@ -133,7 +133,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) # Init exchange - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) # By default only active pairs/markets are to be shown active_only = not args.get('list_pairs_all', False) diff --git a/freqtrade/commands/pairlist_commands.py b/freqtrade/commands/pairlist_commands.py index 9f7a5958e..a815cd5f3 100644 --- a/freqtrade/commands/pairlist_commands.py +++ b/freqtrade/commands/pairlist_commands.py @@ -18,7 +18,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None: from freqtrade.plugins.pairlistmanager import PairListManager config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) quote_currencies = args.get('quote_currencies') if not quote_currencies: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5e1e7d5f8..9fb433dc0 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -70,7 +70,7 @@ class FreqtradeBot(LoggingMixin): validate_config_consistency(config) self.exchange = ExchangeResolver.load_exchange( - self.config['exchange']['name'], self.config, load_leverage_tiers=True) + self.config, load_leverage_tiers=True) init_db(self.config['db_url']) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 622fe4444..d77fc469b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -89,8 +89,7 @@ class Backtesting: self.rejected_df: Dict[str, Dict] = {} self._exchange_name = self.config['exchange']['name'] - self.exchange = ExchangeResolver.load_exchange( - self._exchange_name, self.config, load_leverage_tiers=True) + self.exchange = ExchangeResolver.load_exchange(self.config, load_leverage_tiers=True) self.dataprovider = DataProvider(self.config, self.exchange) if self.config.get('strategy_list'): diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 2eb1c53f5..07c54d720 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -32,7 +32,7 @@ class EdgeCli: # Ensure using dry-run self.config['dry_run'] = True self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT - self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) + self.exchange = ExchangeResolver.load_exchange(self.config) self.strategy = StrategyResolver.load_strategy(self.config) self.strategy.dp = DataProvider(config, self.exchange) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index e415c4911..7fd20f041 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -633,7 +633,7 @@ def load_and_plot_trades(config: Config): """ strategy = StrategyResolver.load_strategy(config) - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) + exchange = ExchangeResolver.load_exchange(config) IStrategy.dp = DataProvider(config, exchange) strategy.ft_bot_start() strategy.bot_loop_start(datetime.now(timezone.utc)) @@ -678,7 +678,7 @@ def plot_profit(config: Config) -> None: if 'timeframe' not in config: raise OperationalException('Timeframe must be set in either config or via --timeframe.') - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) + exchange = ExchangeResolver.load_exchange(config) plot_elements = init_plotscript(config, list(exchange.markets)) trades = plot_elements['trades'] # Filter trades to relevant pairs diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index 54a488e8d..e888028dc 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -19,13 +19,14 @@ class ExchangeResolver(IResolver): object_type = Exchange @staticmethod - def load_exchange(exchange_name: str, config: Config, validate: bool = True, + def load_exchange(config: Config, validate: bool = True, load_leverage_tiers: bool = False) -> Exchange: """ Load the custom class from config parameter :param exchange_name: name of the Exchange to load :param config: configuration dictionary """ + exchange_name: str = config['exchange']['name'] # Map exchange name to avoid duplicate classes for identical exchanges exchange_name = MAP_EXCHANGE_CHILDCLASS.get(exchange_name, exchange_name) exchange_name = exchange_name.title() diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py index f5b1bcd74..bfc1e698c 100644 --- a/freqtrade/rpc/api_server/deps.py +++ b/freqtrade/rpc/api_server/deps.py @@ -46,7 +46,7 @@ def get_exchange(config=Depends(get_config)): if not ApiServer._exchange: from freqtrade.resolvers import ExchangeResolver ApiServer._exchange = ExchangeResolver.load_exchange( - config['exchange']['name'], config, load_leverage_tiers=False) + config, load_leverage_tiers=False) return ApiServer._exchange diff --git a/tests/conftest.py b/tests/conftest.py index 4b725e2db..88fb0bb30 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -181,7 +181,7 @@ def get_patched_exchange(mocker, config, api_mock=None, id='binance', patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes) config['exchange']['name'] = id try: - exchange = ExchangeResolver.load_exchange(id, config, load_leverage_tiers=True) + exchange = ExchangeResolver.load_exchange(config, load_leverage_tiers=True) except ImportError: exchange = Exchange(config) return exchange diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 60855ca54..76a63e57b 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -302,7 +302,7 @@ def exchange(request, exchange_conf): exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False)) exchange_conf['exchange']['name'] = request.param exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] - exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True) + exchange = ExchangeResolver.load_exchange(exchange_conf, validate=True) yield exchange, request.param diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 399442b08..bfe3a9b1d 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -228,27 +228,30 @@ def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch(f'{EXMS}.validate_timeframes') mocker.patch(f'{EXMS}.validate_stakecurrency') mocker.patch(f'{EXMS}.validate_pricing') - - exchange = ExchangeResolver.load_exchange('zaif', default_conf) + default_conf['exchange']['name'] = 'zaif' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() - exchange = ExchangeResolver.load_exchange('Bittrex', default_conf) + default_conf['exchange']['name'] = 'Bittrex' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Bittrex) assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() - exchange = ExchangeResolver.load_exchange('kraken', default_conf) + default_conf['exchange']['name'] = 'kraken' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Kraken) assert not isinstance(exchange, Binance) assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) - exchange = ExchangeResolver.load_exchange('binance', default_conf) + default_conf['exchange']['name'] = 'binance' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Binance) assert not isinstance(exchange, Kraken) @@ -257,7 +260,8 @@ def test_exchange_resolver(default_conf, mocker, caplog): caplog) # Test mapping - exchange = ExchangeResolver.load_exchange('binanceus', default_conf) + default_conf['exchange']['name'] = 'binanceus' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Binance) assert not isinstance(exchange, Kraken) @@ -990,19 +994,20 @@ def test_validate_pricing(default_conf, mocker): mocker.patch(f'{EXMS}.validate_timeframes') mocker.patch(f'{EXMS}.validate_stakecurrency') mocker.patch(f'{EXMS}.name', 'Binance') - ExchangeResolver.load_exchange('binance', default_conf) + default_conf['exchange']['name'] = 'binance' + ExchangeResolver.load_exchange(default_conf) has.update({'fetchTicker': False}) with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): - ExchangeResolver.load_exchange('binance', default_conf) + ExchangeResolver.load_exchange(default_conf) has.update({'fetchTicker': True}) default_conf['exit_pricing']['use_order_book'] = True - ExchangeResolver.load_exchange('binance', default_conf) + ExchangeResolver.load_exchange(default_conf) has.update({'fetchL2OrderBook': False}) with pytest.raises(OperationalException, match="Orderbook not available for .*"): - ExchangeResolver.load_exchange('binance', default_conf) + ExchangeResolver.load_exchange(default_conf) has.update({'fetchL2OrderBook': True}) @@ -1011,7 +1016,7 @@ def test_validate_pricing(default_conf, mocker): default_conf['margin_mode'] = MarginMode.ISOLATED with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): - ExchangeResolver.load_exchange('binance', default_conf) + ExchangeResolver.load_exchange(default_conf) def test_validate_ordertypes(default_conf, mocker): @@ -1091,12 +1096,13 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, 'stoploss_on_exchange': True, 'stoploss_price_type': stopadv, } + default_conf['exchange']['name'] = exchange_name if expected: - ExchangeResolver.load_exchange(exchange_name, default_conf) + ExchangeResolver.load_exchange(default_conf) else: with pytest.raises(OperationalException, match=r'On exchange stoploss price type is not supported for .*'): - ExchangeResolver.load_exchange(exchange_name, default_conf) + ExchangeResolver.load_exchange(default_conf) def test_validate_order_types_not_in_config(default_conf, mocker): From d50e221e62ac47a1608355d80bf76a6bbd582d4b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 08:49:54 +0200 Subject: [PATCH 37/53] Update active ccxt.futures test init --- tests/exchange/test_ccxt_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 76a63e57b..6f5987202 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -330,7 +330,7 @@ def exchange_futures(request, exchange_conf, class_mocker): class_mocker.patch(f'{EXMS}.cache_leverage_tiers') exchange = ExchangeResolver.load_exchange( - request.param, exchange_conf, validate=True, load_leverage_tiers=True) + exchange_conf, validate=True, load_leverage_tiers=True) yield exchange, request.param From 3ae3cc63dfe1553c1859b157aca0742ecc742a8a Mon Sep 17 00:00:00 2001 From: robcaulk Date: Sat, 13 May 2023 11:14:16 +0000 Subject: [PATCH 38/53] fix bug in continual_learning for PyTorch* models --- .../prediction_models/PyTorchMLPClassifier.py | 23 ++++++++++--------- .../prediction_models/PyTorchMLPRegressor.py | 21 +++++++++-------- .../PyTorchTransformerRegressor.py | 23 ++++++++++--------- freqtrade/freqai/torch/PyTorchModelTrainer.py | 6 ++--- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py index ea7981405..b29d20112 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py @@ -74,16 +74,17 @@ class PyTorchMLPClassifier(BasePyTorchClassifier): model.to(self.device) optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate) criterion = torch.nn.CrossEntropyLoss() - init_model = self.get_init_model(dk.pair) - trainer = PyTorchModelTrainer( - model=model, - optimizer=optimizer, - criterion=criterion, - model_meta_data={"class_names": class_names}, - device=self.device, - init_model=init_model, - data_convertor=self.data_convertor, - **self.trainer_kwargs, - ) + # check if continual_learning is activated, and retreive the model to continue training + trainer = self.get_init_model(dk.pair) + if trainer is None: + trainer = PyTorchModelTrainer( + model=model, + optimizer=optimizer, + criterion=criterion, + model_meta_data={"class_names": class_names}, + device=self.device, + data_convertor=self.data_convertor, + **self.trainer_kwargs, + ) trainer.fit(data_dictionary, self.splits) return trainer diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py b/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py index 64f0f4b03..6e1270102 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py @@ -69,15 +69,16 @@ class PyTorchMLPRegressor(BasePyTorchRegressor): model.to(self.device) optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate) criterion = torch.nn.MSELoss() - init_model = self.get_init_model(dk.pair) - trainer = PyTorchModelTrainer( - model=model, - optimizer=optimizer, - criterion=criterion, - device=self.device, - init_model=init_model, - data_convertor=self.data_convertor, - **self.trainer_kwargs, - ) + # check if continual_learning is activated, and retreive the model to continue training + trainer = self.get_init_model(dk.pair) + if trainer is None: + trainer = PyTorchModelTrainer( + model=model, + optimizer=optimizer, + criterion=criterion, + device=self.device, + data_convertor=self.data_convertor, + **self.trainer_kwargs, + ) trainer.fit(data_dictionary, self.splits) return trainer diff --git a/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py b/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py index e760f6e68..5e84ada72 100644 --- a/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py +++ b/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py @@ -75,17 +75,18 @@ class PyTorchTransformerRegressor(BasePyTorchRegressor): model.to(self.device) optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate) criterion = torch.nn.MSELoss() - init_model = self.get_init_model(dk.pair) - trainer = PyTorchTransformerTrainer( - model=model, - optimizer=optimizer, - criterion=criterion, - device=self.device, - init_model=init_model, - data_convertor=self.data_convertor, - window_size=self.window_size, - **self.trainer_kwargs, - ) + # check if continual_learning is activated, and retreive the model to continue training + trainer = self.get_init_model(dk.pair) + if trainer is None: + trainer = PyTorchTransformerTrainer( + model=model, + optimizer=optimizer, + criterion=criterion, + device=self.device, + data_convertor=self.data_convertor, + window_size=self.window_size, + **self.trainer_kwargs, + ) trainer.fit(data_dictionary, self.splits) return trainer diff --git a/freqtrade/freqai/torch/PyTorchModelTrainer.py b/freqtrade/freqai/torch/PyTorchModelTrainer.py index a3b0d9b9c..a25fa45bc 100644 --- a/freqtrade/freqai/torch/PyTorchModelTrainer.py +++ b/freqtrade/freqai/torch/PyTorchModelTrainer.py @@ -25,7 +25,7 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): optimizer: Optimizer, criterion: nn.Module, device: str, - init_model: Dict, + # init_model: Dict, data_convertor: PyTorchDataConvertor, model_meta_data: Dict[str, Any] = {}, window_size: int = 1, @@ -56,8 +56,8 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): self.max_n_eval_batches: Optional[int] = kwargs.get("max_n_eval_batches", None) self.data_convertor = data_convertor self.window_size: int = window_size - if init_model: - self.load_from_checkpoint(init_model) + # if init_model: + # self.load_from_checkpoint(init_model) def fit(self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str]): """ From fad1c198562a64464c78fb5816a6e4700a77457f Mon Sep 17 00:00:00 2001 From: robcaulk Date: Sat, 13 May 2023 11:21:43 +0000 Subject: [PATCH 39/53] add warnings in the doc for users to better understand the limitations of continual_learning --- docs/freqai-running.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/freqai-running.md b/docs/freqai-running.md index f3ccc546f..47d2ec4b3 100644 --- a/docs/freqai-running.md +++ b/docs/freqai-running.md @@ -131,6 +131,9 @@ You can choose to adopt a continual learning scheme by setting `"continual_learn ???+ danger "Continual learning enforces a constant parameter space" Since `continual_learning` means that the model parameter space *cannot* change between trainings, `principal_component_analysis` is automatically disabled when `continual_learning` is enabled. Hint: PCA changes the parameter space and the number of features, learn more about PCA [here](freqai-feature-engineering.md#data-dimensionality-reduction-with-principal-component-analysis). +???+ danger "Experimental functionality" + Beware that this is currently a naive approach to incremental learning, and it has a high probability of overfitting/getting stuck in local minima while the market moves away from your model. We have the mechanics available in FreqAI primarily for experimental purposes and so that it is ready for more mature approaches to continual learning in chaotic systems like the crypto market. + ## Hyperopt You can hyperopt using the same command as for [typical Freqtrade hyperopt](hyperopt.md): From 2ec1302c109dfbdd13bfe3db3e0313b561eeb522 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Sat, 13 May 2023 11:23:57 +0000 Subject: [PATCH 40/53] add warnings in the doc for users to better understand the limitations of continual_learning --- docs/freqai-parameter-table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 76c175304..ef1a23401 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -18,7 +18,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `purge_old_models` | Number of models to keep on disk (not relevant to backtesting). Default is 2, which means that dry/live runs will keep the latest 2 models on disk. Setting to 0 keeps all models. This parameter also accepts a boolean to maintain backwards compatibility.
**Datatype:** Integer.
Default: `2`. | `save_backtest_models` | Save models to disk when running backtesting. Backtesting operates most efficiently by saving the prediction data and reusing them directly for subsequent runs (when you wish to tune entry/exit parameters). Saving backtesting models to disk also allows to use the same model files for starting a dry/live instance with the same model `identifier`.
**Datatype:** Boolean.
Default: `False` (no models are saved). | `fit_live_predictions_candles` | Number of historical candles to use for computing target (label) statistics from prediction data, instead of from the training dataset (more information can be found [here](freqai-configuration.md#creating-a-dynamic-target-threshold)).
**Datatype:** Positive integer. -| `continual_learning` | Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning (more information can be found [here](freqai-running.md#continual-learning)).
**Datatype:** Boolean.
Default: `False`. +| `continual_learning` | Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning (more information can be found [here](freqai-running.md#continual-learning)). Beware that this is currently a naive approach to incremental learning, and it has a high probability of overfitting/getting stuck in local minima while the market moves away from your model. We have the connections here primarily for experimental purposes and so that it is ready for more mature approaches to continual learning in chaotic systems like the crypto market.
**Datatype:** Boolean.
Default: `False`. | `write_metrics_to_disk` | Collect train timings, inference timings and cpu usage in json file.
**Datatype:** Boolean.
Default: `False` | `data_kitchen_thread_count` |
Designate the number of threads you want to use for data processing (outlier methods, normalization, etc.). This has no impact on the number of threads used for training. If user does not set it (default), FreqAI will use max number of threads - 2 (leaving 1 physical core available for Freqtrade bot and FreqUI)
**Datatype:** Positive integer. From 18c1eda09b281ed43fdff3e55de6e29cd8cc1ab6 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Sat, 13 May 2023 11:27:36 +0000 Subject: [PATCH 41/53] remove commented lines --- freqtrade/freqai/torch/PyTorchModelTrainer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/freqtrade/freqai/torch/PyTorchModelTrainer.py b/freqtrade/freqai/torch/PyTorchModelTrainer.py index a25fa45bc..a9310a182 100644 --- a/freqtrade/freqai/torch/PyTorchModelTrainer.py +++ b/freqtrade/freqai/torch/PyTorchModelTrainer.py @@ -25,7 +25,6 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): optimizer: Optimizer, criterion: nn.Module, device: str, - # init_model: Dict, data_convertor: PyTorchDataConvertor, model_meta_data: Dict[str, Any] = {}, window_size: int = 1, @@ -56,8 +55,6 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): self.max_n_eval_batches: Optional[int] = kwargs.get("max_n_eval_batches", None) self.data_convertor = data_convertor self.window_size: int = window_size - # if init_model: - # self.load_from_checkpoint(init_model) def fit(self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str]): """ From 0d4010c38c176e4c27bb110ff740e4f361e94aee Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 16:13:37 +0200 Subject: [PATCH 42/53] maint: Remove faulty config setting from default_conf --- tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 88fb0bb30..70d15c6df 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -493,7 +493,6 @@ def get_default_conf(testdatadir): }, "exchange": { "name": "binance", - "enabled": True, "key": "key", "secret": "secret", "pair_whitelist": [ From af95d56cebd66be188d06ca55d91e1084cec92c8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 13:43:32 +0200 Subject: [PATCH 43/53] Import deepcopy specifically --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9fb433dc0..fa422d107 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1,9 +1,9 @@ """ Freqtrade is the main module of this bot. It contains the class Freqtrade() """ -import copy import logging import traceback +from copy import deepcopy from datetime import datetime, time, timedelta, timezone from math import isclose from threading import Lock @@ -461,7 +461,7 @@ class FreqtradeBot(LoggingMixin): """ trades_created = 0 - whitelist = copy.deepcopy(self.active_pair_whitelist) + whitelist = deepcopy(self.active_pair_whitelist) if not whitelist: self.log_once("Active pair whitelist is empty.", logger.info) return trades_created From dc4268b6e767c419ae5258a223783ebe185e726f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 13:38:51 +0200 Subject: [PATCH 44/53] Convert Exchange arguments to be kw only --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 5273030ab..27c96a70d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -92,7 +92,7 @@ class Exchange: # TradingMode.SPOT always supported and not required in this list ] - def __init__(self, config: Config, validate: bool = True, + def __init__(self, config: Config, *, validate: bool = True, load_leverage_tiers: bool = False) -> None: """ Initializes this module with the given config, From 0db186935618ac57fef5a30787e9d4ff78afcc9f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 16:22:04 +0200 Subject: [PATCH 45/53] Update cached binance leverage tiers --- .../exchange/binance_leverage_tiers.json | 1460 +++++++++++++++-- 1 file changed, 1291 insertions(+), 169 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 0b9be0f55..0f252f63e 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -1,4 +1,118 @@ { + "1000FLOKI/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "800000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "15650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 800000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "800000", + "maintMarginRatio": "0.125", + "cum": "35650.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "160650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "910650.0" + } + } + ], "1000LUNC/BUSD:BUSD": [ { "tier": 1.0, @@ -211,6 +325,120 @@ } } ], + "1000PEPE/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "600000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "30650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "70650.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "320650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.5", + "cum": "1820650.0" + } + } + ], "1000SHIB/BUSD:BUSD": [ { "tier": 1.0, @@ -2174,10 +2402,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -2190,10 +2418,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "8", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -2206,10 +2434,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -2252,13 +2480,13 @@ "tier": 6.0, "currency": "BUSD", "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "1200000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", "cum": "386900.0" @@ -4821,6 +5049,120 @@ } } ], + "BLUR/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "600000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "30650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "70650.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "320650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.5", + "cum": "1820650.0" + } + } + ], "BLZ/USDT:USDT": [ { "tier": 1.0, @@ -8544,10 +8886,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -8560,10 +8902,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "15", + "initialLeverage": "8", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -8576,10 +8918,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "10", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -8638,13 +8980,13 @@ "tier": 7.0, "currency": "BUSD", "minNotional": 3000000.0, - "maxNotional": 8000000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "8000000", + "notionalCap": "4000000", "notionalFloor": "3000000", "maintMarginRatio": "0.5", "cum": "949400.0" @@ -9041,6 +9383,120 @@ } } ], + "EDU/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "23150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], "EGLD/USDT:USDT": [ { "tier": 1.0, @@ -9552,10 +10008,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -9568,10 +10024,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 7.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "7", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -9584,10 +10040,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -9630,13 +10086,13 @@ "tier": 6.0, "currency": "BUSD", "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "1500000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", "cum": "386900.0" @@ -9805,6 +10261,168 @@ } } ], + "ETH/BTC:BTC": [ + { + "tier": 1.0, + "currency": "BTC", + "minNotional": 0.0, + "maxNotional": 5.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5", + "notionalFloor": "0", + "maintMarginRatio": "0.005", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "BTC", + "minNotional": 5.0, + "maxNotional": 10.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10", + "notionalFloor": "5", + "maintMarginRatio": "0.006", + "cum": "0.005" + } + }, + { + "tier": 3.0, + "currency": "BTC", + "minNotional": 10.0, + "maxNotional": 100.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "100", + "notionalFloor": "10", + "maintMarginRatio": "0.01", + "cum": "0.045" + } + }, + { + "tier": 4.0, + "currency": "BTC", + "minNotional": 100.0, + "maxNotional": 250.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "250", + "notionalFloor": "100", + "maintMarginRatio": "0.02", + "cum": "1.045" + } + }, + { + "tier": 5.0, + "currency": "BTC", + "minNotional": 250.0, + "maxNotional": 800.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "800", + "notionalFloor": "250", + "maintMarginRatio": "0.025", + "cum": "2.295" + } + }, + { + "tier": 6.0, + "currency": "BTC", + "minNotional": 800.0, + "maxNotional": 1500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 8.0, + "info": { + "bracket": "6", + "initialLeverage": "8", + "notionalCap": "1500", + "notionalFloor": "800", + "maintMarginRatio": "0.05", + "cum": "22.295" + } + }, + { + "tier": 7.0, + "currency": "BTC", + "minNotional": 1500.0, + "maxNotional": 2000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "7", + "initialLeverage": "5", + "notionalCap": "2000", + "notionalFloor": "1500", + "maintMarginRatio": "0.1", + "cum": "97.295" + } + }, + { + "tier": 8.0, + "currency": "BTC", + "minNotional": 2000.0, + "maxNotional": 3000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "3000", + "notionalFloor": "2000", + "maintMarginRatio": "0.125", + "cum": "147.295" + } + }, + { + "tier": 9.0, + "currency": "BTC", + "minNotional": 3000.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "5000", + "notionalFloor": "3000", + "maintMarginRatio": "0.25", + "cum": "522.295" + } + }, + { + "tier": 10.0, + "currency": "BTC", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "10", + "initialLeverage": "1", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.5", + "cum": "1772.295" + } + } + ], "ETH/BUSD:BUSD": [ { "tier": 1.0, @@ -10364,10 +10982,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -10380,10 +10998,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 7.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "7", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -10396,10 +11014,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -10442,13 +11060,13 @@ "tier": 6.0, "currency": "BUSD", "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", "cum": "386900.0" @@ -13341,6 +13959,120 @@ } } ], + "IDEX/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "23150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], "IMX/USDT:USDT": [ { "tier": 1.0, @@ -13492,13 +14224,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 600000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "600000", + "notionalCap": "1200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -13507,65 +14239,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 1600000.0, + "minNotional": 1200000.0, + "maxNotional": 3200000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "600000", + "notionalCap": "3200000", + "notionalFloor": "1200000", "maintMarginRatio": "0.1", - "cum": "30650.0" + "cum": "60650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, + "minNotional": 3200000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", + "notionalCap": "4000000", + "notionalFloor": "3200000", "maintMarginRatio": "0.125", - "cum": "70650.0" + "cum": "140650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 6000000.0, + "minNotional": 4000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "6000000", - "notionalFloor": "2000000", + "notionalCap": "12000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.25", - "cum": "320650.0" + "cum": "640650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "1820650.0" + "cum": "3640650.0" } } ], @@ -15562,13 +16294,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 200000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "200000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -15577,65 +16309,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "10650.0" + "cum": "30650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.125", - "cum": "23150.0" + "cum": "70650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.25", - "cum": "148150.0" + "cum": "320650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "898150.0" + "cum": "1820650.0" } } ], @@ -17746,10 +18478,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -17762,10 +18494,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -17778,10 +18510,10 @@ "minNotional": 25000.0, "maxNotional": 900000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "900000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -18202,10 +18934,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -18218,10 +18950,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -18232,13 +18964,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "300000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -18247,33 +18979,33 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 300000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "800000", + "notionalFloor": "300000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "15650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 800000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "800000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "35650.0" } }, { @@ -18281,15 +19013,31 @@ "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "160650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "910650.0" } } ], @@ -18815,6 +19563,120 @@ } } ], + "RAD/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "23150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], "RAY/USDT:USDT": [ { "tier": 1.0, @@ -18950,13 +19812,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 200000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "200000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -18965,65 +19827,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "10650.0" + "cum": "30650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.125", - "cum": "23150.0" + "cum": "70650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.25", - "cum": "148150.0" + "cum": "320650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "898150.0" + "cum": "1820650.0" } } ], @@ -21412,13 +22274,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.025", "cum": "75.0" @@ -21427,39 +22289,39 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 400000.0, + "minNotional": 50000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "400000", - "notionalFloor": "25000", + "notionalCap": "600000", + "notionalFloor": "50000", "maintMarginRatio": "0.05", - "cum": "700.0" + "cum": "1325.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 1000000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "400000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "20700.0" + "cum": "31325.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 1600000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, @@ -21467,9 +22329,9 @@ "bracket": "5", "initialLeverage": "4", "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.125", - "cum": "45700.0" + "cum": "71325.0" } }, { @@ -21485,7 +22347,7 @@ "notionalCap": "6000000", "notionalFloor": "2000000", "maintMarginRatio": "0.25", - "cum": "295700.0" + "cum": "321325.0" } }, { @@ -21501,7 +22363,137 @@ "notionalCap": "10000000", "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "1795700.0" + "cum": "1821325.0" + } + } + ], + "SUI/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "300.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "7800.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "37800.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "77800.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "327800.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.5", + "cum": "1827800.0" } } ], @@ -22759,6 +23751,120 @@ } } ], + "UMA/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "23150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], "UNFI/USDT:USDT": [ { "tier": 1.0, @@ -24818,10 +25924,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -24834,10 +25940,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -24848,13 +25954,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -24863,49 +25969,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "30700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "70700.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "320700.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "1820700.0" } } ], From e76356aff5a55749a1e53f958804a9f0f9dc186b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 May 2023 07:23:38 +0200 Subject: [PATCH 46/53] Bump catboost to 1.2, disable some constraints --- requirements-freqai.txt | 2 +- tests/freqai/test_freqai_interface.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index e5bc23d56..9f6390e56 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -5,7 +5,7 @@ # Required for freqai scikit-learn==1.1.3 joblib==1.2.0 -catboost==1.1.1; platform_machine != 'aarch64' and 'arm' not in platform_machine and python_version < '3.11' +catboost==1.2; 'arm' not in platform_machine lightgbm==3.3.5 xgboost==1.7.5 tensorboard==2.13.0 diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index e27c8d2c0..95efaac52 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -34,7 +34,7 @@ def is_mac() -> bool: def can_run_model(model: str) -> None: - if (is_arm() or is_py11()) and "Catboost" in model: + if is_arm() and "Catboost" in model: pytest.skip("CatBoost is not supported on ARM.") is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model From 106db716f8c1cd351d321f2056422c9b6280cf06 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 16:30:18 +0200 Subject: [PATCH 47/53] Force smaller catboost version on 3.8 macos --- requirements-freqai.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 9f6390e56..60eaad131 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -5,6 +5,7 @@ # Required for freqai scikit-learn==1.1.3 joblib==1.2.0 +catboost==1.1.1; platform_machine == 'darwin' and python_version < '3.9' catboost==1.2; 'arm' not in platform_machine lightgbm==3.3.5 xgboost==1.7.5 From 838fbb76abc26a6e373114cc9718824d936ed062 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 16:43:45 +0200 Subject: [PATCH 48/53] Improve version constraints --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 60eaad131..5dbf70409 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -6,7 +6,7 @@ scikit-learn==1.1.3 joblib==1.2.0 catboost==1.1.1; platform_machine == 'darwin' and python_version < '3.9' -catboost==1.2; 'arm' not in platform_machine +catboost==1.2; 'arm' not in platform_machine and (platform_machine != 'darwin' or python_version >= '3.9') lightgbm==3.3.5 xgboost==1.7.5 tensorboard==2.13.0 From 784087384c57c8bb379ec86d6262bf82db59291f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 17:22:11 +0200 Subject: [PATCH 49/53] darwin excludes must use "sys_platform" --- requirements-freqai.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 5dbf70409..ad069ade2 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -5,8 +5,8 @@ # Required for freqai scikit-learn==1.1.3 joblib==1.2.0 -catboost==1.1.1; platform_machine == 'darwin' and python_version < '3.9' -catboost==1.2; 'arm' not in platform_machine and (platform_machine != 'darwin' or python_version >= '3.9') +catboost==1.1.1; sys_platform == 'darwin' and python_version < '3.9' +catboost==1.2; 'arm' not in platform_machine and (sys_platform != 'darwin' or python_version >= '3.9') lightgbm==3.3.5 xgboost==1.7.5 tensorboard==2.13.0 From 726627976817faadf6f7905cef0d98de1d5df66f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 20:22:15 +0200 Subject: [PATCH 50/53] Improve docs around pytho 3.11 --- .github/workflows/ci.yml | 2 +- docs/freqai.md | 6 +----- setup.sh | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd9bf02fb..9ecd27cc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -336,7 +336,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" - name: Documentation build run: | diff --git a/docs/freqai.md b/docs/freqai.md index b3cd8cd07..02b723a20 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -72,11 +72,7 @@ pip install -r requirements-freqai.txt ``` !!! Note - Catboost will not be installed on arm devices (raspberry, Mac M1, ARM based VPS, ...), since it does not provide wheels for this platform. - -!!! Note "python 3.11" - Some dependencies (Catboost, Torch) currently don't support python 3.11. Freqtrade therefore only supports python 3.10 for these models/dependencies. - Tests involving these dependencies are skipped on 3.11. + Catboost will not be installed on low-powered arm devices (raspberry), since it does not provide wheels for this platform. ### Usage with docker diff --git a/setup.sh b/setup.sh index cc7f3e2e6..84f804021 100755 --- a/setup.sh +++ b/setup.sh @@ -258,7 +258,7 @@ function install() { install_redhat else echo "This script does not support your OS." - echo "If you have Python version 3.8 - 3.10, pip, virtualenv, ta-lib you can continue." + echo "If you have Python version 3.8 - 3.11, pip, virtualenv, ta-lib you can continue." echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." sleep 10 fi From 66a97ff45de3e3ba39bf54a94657c8f52ba88536 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 May 2023 20:43:37 +0200 Subject: [PATCH 51/53] Remove some utcnow usages --- freqtrade/freqtradebot.py | 4 ++-- tests/persistence/test_persistence.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 436564b42..d068ef6e3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1018,7 +1018,7 @@ class FreqtradeBot(LoggingMixin): 'base_currency': self.exchange.get_pair_base_currency(trade.pair), 'fiat_currency': self.config.get('fiat_display_currency', None), 'amount': order.safe_amount_after_fee if fill else (order.amount or trade.amount), - 'open_date': trade.open_date or datetime.utcnow(), + 'open_date': trade.open_date or datetime.now(timezone.utc), 'current_rate': current_rate, 'sub_trade': sub_trade, } @@ -1742,7 +1742,7 @@ class FreqtradeBot(LoggingMixin): 'sell_reason': trade.exit_reason, # Deprecated 'exit_reason': trade.exit_reason, 'open_date': trade.open_date, - 'close_date': trade.close_date or datetime.utcnow(), + 'close_date': trade.close_date or datetime.now(timezone.utc), 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], 'base_currency': self.exchange.get_pair_base_currency(trade.pair), diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 1a7d84eca..6af629c75 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -239,7 +239,7 @@ def test_interest(fee, exchange, is_short, lev, minutes, rate, interest, stake_amount=20.0, amount=30.0, open_rate=2.0, - open_date=datetime.utcnow() - timedelta(minutes=minutes), + open_date=datetime.now(timezone.utc) - timedelta(minutes=minutes), fee_open=fee.return_value, fee_close=fee.return_value, exchange=exchange, @@ -2063,7 +2063,7 @@ def test_trade_truncates_string_fields(): stake_amount=20.0, amount=30.0, open_rate=2.0, - open_date=datetime.utcnow() - timedelta(minutes=20), + open_date=datetime.now(timezone.utc) - timedelta(minutes=20), fee_open=0.001, fee_close=0.001, exchange='binance', From bbce738523275eecd7f3850ee5d453ac545098c3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 May 2023 08:42:30 +0200 Subject: [PATCH 52/53] Improve tests around timezone --- tests/plugins/test_protections.py | 10 +++++----- tests/rpc/test_rpc.py | 5 ++--- tests/rpc/test_rpc_apiserver.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 15 ++++++++------- tests/strategy/test_default_strategy.py | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 5e6128c73..8fe8cec6b 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -1,5 +1,5 @@ import random -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import pytest @@ -24,8 +24,8 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool, stake_amount=0.01, fee_open=fee, fee_close=fee, - open_date=datetime.utcnow() - timedelta(minutes=min_ago_open or 200), - close_date=datetime.utcnow() - timedelta(minutes=min_ago_close or 30), + open_date=datetime.now(timezone.utc) - timedelta(minutes=min_ago_open or 200), + close_date=datetime.now(timezone.utc) - timedelta(minutes=min_ago_close or 30), open_rate=open_rate, is_open=is_open, amount=0.01 / open_rate, @@ -87,9 +87,9 @@ def test_protectionmanager(mocker, default_conf): for handler in freqtrade.protections._protection_handlers: assert handler.name in constants.AVAILABLE_PROTECTIONS if not handler.has_global_stop: - assert handler.global_stop(datetime.utcnow(), '*') is None + assert handler.global_stop(datetime.now(timezone.utc), '*') is None if not handler.has_local_stop: - assert handler.stop_per_pair('XRP/BTC', datetime.utcnow(), '*') is None + assert handler.stop_per_pair('XRP/BTC', datetime.now(timezone.utc), '*') is None @pytest.mark.parametrize('timeframe,expected,protconf', [ diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index bb84ff8e9..87b2475ca 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -261,8 +261,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert isnan(fiat_profit_sum) -def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee, - limit_buy_order, limit_sell_order, markets, mocker) -> None: +def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee, markets, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( EXMS, @@ -295,7 +294,7 @@ def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee, assert day['starting_balance'] in (pytest.approx(1062.37), pytest.approx(1066.46)) assert day['fiat_value'] in (0.0, ) # ensure first day is current date - assert str(days['data'][0]['date']) == str(datetime.utcnow().date()) + assert str(days['data'][0]['date']) == str(datetime.now(timezone.utc).date()) # Try invalid data with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 51fddbb88..78e713391 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -601,7 +601,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): assert len(rc.json()['data']) == 7 assert rc.json()['stake_currency'] == 'BTC' assert rc.json()['fiat_display_currency'] == 'USD' - assert rc.json()['data'][0]['date'] == str(datetime.utcnow().date()) + assert rc.json()['data'][0]['date'] == str(datetime.now(timezone.utc).date()) @pytest.mark.parametrize('is_short', [True, False]) @@ -1224,7 +1224,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): stake_amount=1, open_rate=0.245441, open_order_id="123456", - open_date=datetime.utcnow(), + open_date=datetime.now(timezone.utc), is_open=False, is_short=False, fee_close=fee.return_value, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 02e829b64..0d8c98d29 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -52,7 +52,7 @@ def default_conf(default_conf) -> dict: @pytest.fixture def update(): - message = Message(0, datetime.utcnow(), Chat(0, 0)) + message = Message(0, datetime.now(timezone.utc), Chat(0, 0)) _update = Update(0, message=message) return _update @@ -213,7 +213,7 @@ async def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> Non patch_exchange(mocker) caplog.set_level(logging.DEBUG) chat = Chat(0xdeadbeef, 0) - message = Message(randint(1, 100), datetime.utcnow(), chat) + message = Message(randint(1, 100), datetime.now(timezone.utc), chat) update = Update(randint(1, 100), message=message) default_conf['telegram']['enabled'] = False @@ -520,7 +520,7 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time assert msg_mock.call_count == 1 assert "Daily Profit over the last 2 days:" in msg_mock.call_args_list[0][0][0] assert 'Day ' in msg_mock.call_args_list[0][0][0] - assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] + assert str(datetime.now(timezone.utc).date()) in msg_mock.call_args_list[0][0][0] assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0] assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0] assert '(2)' in msg_mock.call_args_list[0][0][0] @@ -533,8 +533,9 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time await telegram._daily(update=update, context=context) assert msg_mock.call_count == 1 assert "Daily Profit over the last 7 days:" in msg_mock.call_args_list[0][0][0] - assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] - assert str((datetime.utcnow() - timedelta(days=5)).date()) in msg_mock.call_args_list[0][0][0] + assert str(datetime.now(timezone.utc).date()) in msg_mock.call_args_list[0][0][0] + assert str((datetime.now(timezone.utc) - timedelta(days=5)).date() + ) in msg_mock.call_args_list[0][0][0] assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0] assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0] assert '(2)' in msg_mock.call_args_list[0][0][0] @@ -608,7 +609,7 @@ async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, tim assert "Weekly Profit over the last 2 weeks (starting from Monday):" \ in msg_mock.call_args_list[0][0][0] assert 'Monday ' in msg_mock.call_args_list[0][0][0] - today = datetime.utcnow().date() + today = datetime.now(timezone.utc).date() first_iso_day_of_current_week = today - timedelta(days=today.weekday()) assert str(first_iso_day_of_current_week) in msg_mock.call_args_list[0][0][0] assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0] @@ -677,7 +678,7 @@ async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, ti assert msg_mock.call_count == 1 assert 'Monthly Profit over the last 2 months:' in msg_mock.call_args_list[0][0][0] assert 'Month ' in msg_mock.call_args_list[0][0][0] - today = datetime.utcnow().date() + today = datetime.now(timezone.utc).date() current_month = f"{today.year}-{today.month:02} " assert current_month in msg_mock.call_args_list[0][0][0] assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0] diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index cb3d61e89..5f41177eb 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone import pytest from pandas import DataFrame @@ -43,12 +43,12 @@ def test_strategy_test_v3(dataframe_1m, fee, is_short, side): assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1, rate=20000, time_in_force='gtc', - current_time=datetime.utcnow(), + current_time=datetime.now(timezone.utc), side=side, entry_tag=None) is True assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1, rate=20000, time_in_force='gtc', exit_reason='roi', sell_reason='roi', - current_time=datetime.utcnow(), + current_time=datetime.now(timezone.utc), side=side) is True assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(), From af8fbad2814ff38a957eed5e07bda1a8a42e91b9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 May 2023 08:54:26 +0200 Subject: [PATCH 53/53] Improve Date timezone useage --- freqtrade/freqtradebot.py | 6 +++--- freqtrade/persistence/trade_model.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d068ef6e3..ef480a8e2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1018,7 +1018,7 @@ class FreqtradeBot(LoggingMixin): 'base_currency': self.exchange.get_pair_base_currency(trade.pair), 'fiat_currency': self.config.get('fiat_display_currency', None), 'amount': order.safe_amount_after_fee if fill else (order.amount or trade.amount), - 'open_date': trade.open_date or datetime.now(timezone.utc), + 'open_date': trade.open_date_utc or datetime.now(timezone.utc), 'current_rate': current_rate, 'sub_trade': sub_trade, } @@ -1741,8 +1741,8 @@ class FreqtradeBot(LoggingMixin): 'enter_tag': trade.enter_tag, 'sell_reason': trade.exit_reason, # Deprecated 'exit_reason': trade.exit_reason, - 'open_date': trade.open_date, - 'close_date': trade.close_date or datetime.now(timezone.utc), + 'open_date': trade.open_date_utc, + 'close_date': trade.close_date_utc or datetime.now(timezone.utc), 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], 'base_currency': self.exchange.get_pair_base_currency(trade.pair), diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index cff2c37f0..cc72e2bf0 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -425,7 +425,7 @@ class LocalTrade(): @property def close_date_utc(self): - return self.close_date.replace(tzinfo=timezone.utc) + return self.close_date.replace(tzinfo=timezone.utc) if self.close_date else None @property def entry_side(self) -> str: