From 8e98a2ff9f4fabf81bf5a4f4e1f772f5c4a091ec Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 16:42:18 +0200 Subject: [PATCH] api - provide assset_currency via API --- freqtrade/exchange/exchange.py | 8 ++------ freqtrade/freqtradebot.py | 3 +++ freqtrade/optimize/backtesting.py | 3 +++ freqtrade/persistence/migrations.py | 14 ++++++++----- freqtrade/persistence/models.py | 26 +++++++++++++++++++++++++ freqtrade/rpc/api_server/api_schemas.py | 2 ++ freqtrade/rpc/rpc.py | 1 - freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc.py | 6 ++++-- tests/rpc/test_rpc_apiserver.py | 8 ++++++-- tests/rpc/test_rpc_telegram.py | 3 ++- tests/test_persistence.py | 4 ++++ 12 files changed, 62 insertions(+), 18 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 609dbb83e..82505759a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -341,15 +341,11 @@ class Exchange: return sorted(set([x['quote'] for _, x in markets.items()])) def get_pair_quote_currency(self, pair: str) -> str: - """ - Return a pair's quote currency - """ + """ Return a pair's quote currency (base/quote:settlement) """ return self.markets.get(pair, {}).get('quote', '') def get_pair_base_currency(self, pair: str) -> str: - """ - Return a pair's base currency - """ + """ Return a pair's base currency (base/quote:settlement) """ return self.markets.get(pair, {}).get('base', '') def market_is_future(self, market: Dict[str, Any]) -> bool: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dc2e21ed6..57d7cac3c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -676,6 +676,7 @@ class FreqtradeBot(LoggingMixin): # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') + base_currency = self.exchange.get_pair_base_currency(pair) open_date = datetime.now(timezone.utc) funding_fees = self.exchange.get_funding_fees( pair=pair, amount=amount, is_short=is_short, open_date=open_date) @@ -683,6 +684,8 @@ class FreqtradeBot(LoggingMixin): if trade is None: trade = Trade( pair=pair, + base_currency=base_currency, + stake_currency=self.config['stake_currency'], stake_amount=stake_amount, amount=amount, is_open=True, diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 4bb10d39c..438337669 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -726,6 +726,7 @@ class Backtesting: if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): self.order_id_counter += 1 + base_currency = self.exchange.get_pair_base_currency(pair) amount = round((stake_amount / propose_rate) * leverage, 8) is_short = (direction == 'short') # Necessary for Margin trading. Disabled until support is enabled. @@ -738,6 +739,8 @@ class Backtesting: id=self.trade_id_counter, open_order_id=self.order_id_counter, pair=pair, + base_currency=base_currency, + stake_currency=self.config['stake_currency'], open_rate=propose_rate, open_rate_requested=propose_rate, open_date=current_time, diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 9521eae69..996af7341 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -58,6 +58,8 @@ def migrate_trades_and_orders_table( decl_base, inspector, engine, trade_back_name: str, cols: List, order_back_name: str, cols_order: List): + base_currency = get_column_def(cols, 'base_currency', 'null') + stake_currency = get_column_def(cols, 'stake_currency', 'null') fee_open = get_column_def(cols, 'fee_open', 'fee') fee_open_cost = get_column_def(cols, 'fee_open_cost', 'null') fee_open_currency = get_column_def(cols, 'fee_open_currency', 'null') @@ -130,7 +132,7 @@ def migrate_trades_and_orders_table( # Copy data back - following the correct schema with engine.begin() as connection: connection.execute(text(f"""insert into trades - (id, exchange, pair, is_open, + (id, exchange, pair, base_currency, stake_currency, is_open, fee_open, fee_open_cost, fee_open_currency, fee_close, fee_close_cost, fee_close_currency, open_rate, open_rate_requested, close_rate, close_rate_requested, close_profit, @@ -142,7 +144,8 @@ def migrate_trades_and_orders_table( trading_mode, leverage, liquidation_price, is_short, interest_rate, funding_fees ) - select id, lower(exchange), pair, + select id, lower(exchange), pair, {base_currency} base_currency, + {stake_currency} stake_currency, is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost, {fee_open_currency} fee_open_currency, {fee_close} fee_close, {fee_close_cost} fee_close_cost, {fee_close_currency} fee_close_currency, @@ -230,7 +233,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None: """ inspector = inspect(engine) - cols = inspector.get_columns('trades') + cols_trades = inspector.get_columns('trades') cols_orders = inspector.get_columns('orders') tabs = get_table_names_for_table(inspector, 'trades') table_back_name = get_backup_name(tabs, 'trades_bak') @@ -241,11 +244,12 @@ def check_migrate(engine, decl_base, previous_tables) -> None: # Migrates both trades and orders table! # if ('orders' not in previous_tables # or not has_column(cols_orders, 'leverage')): - if not has_column(cols, 'exit_order_status'): + if not has_column(cols_trades, 'base_currency'): logger.info(f"Running database migration for trades - " f"backup: {table_back_name}, {order_table_bak_name}") migrate_trades_and_orders_table( - decl_base, inspector, engine, table_back_name, cols, order_table_bak_name, cols_orders) + decl_base, inspector, engine, table_back_name, cols_trades, + order_table_bak_name, cols_orders) if 'orders' not in previous_tables and 'trades' in previous_tables: logger.info('Moving open orders to Orders table.') diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 3cd9cbd67..05de39caf 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -279,6 +279,8 @@ class LocalTrade(): exchange: str = '' pair: str = '' + base_currency: str = '' + stake_currency: str = '' is_open: bool = True fee_open: float = 0.0 fee_open_cost: Optional[float] = None @@ -397,6 +399,26 @@ class LocalTrade(): else: return "long" + @property + def safe_base_currency(self) -> str: + """ + Compatibility layer for asset - which can be empty for old trades. + """ + try: + return self.base_currency or self.pair.split('/')[0] + except IndexError: + return '' + + @property + def safe_quote_currency(self) -> str: + """ + Compatibility layer for asset - which can be empty for old trades. + """ + try: + return self.stake_currency or self.pair.split('/')[1].split(':')[0] + except IndexError: + return '' + def __init__(self, **kwargs): for key in kwargs: setattr(self, key, kwargs[key]) @@ -423,6 +445,8 @@ class LocalTrade(): return { 'trade_id': self.id, 'pair': self.pair, + 'base_currency': self.safe_base_currency, + 'quote_currency': self.safe_quote_currency, 'is_open': self.is_open, 'exchange': self.exchange, 'amount': round(self.amount, 8), @@ -1051,6 +1075,8 @@ class Trade(_DECL_BASE, LocalTrade): exchange = Column(String(25), nullable=False) pair = Column(String(25), nullable=False, index=True) + base_currency = Column(String(25), nullable=True) + stake_currency = Column(String(25), nullable=True) is_open = Column(Boolean, nullable=False, default=True, index=True) fee_open = Column(Float, nullable=False, default=0.0) fee_open_cost = Column(Float, nullable=True) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 03049e0f4..ae797edad 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -203,6 +203,8 @@ class OrderSchema(BaseModel): class TradeSchema(BaseModel): trade_id: int pair: str + base_currency: str + quote_currency: str is_open: bool is_short: bool exchange: str diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 8f3d57cf6..be0e8e797 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -197,7 +197,6 @@ class RPC: trade_dict = trade.to_json() trade_dict.update(dict( - base_currency=self._freqtrade.config['stake_currency'], close_profit=trade.close_profit if trade.close_profit is not None else None, current_rate=current_rate, current_profit=current_profit, # Deprecated diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e13e46395..5f6a8b147 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -508,7 +508,7 @@ class Telegram(RPCHandler): lines.append("*Open Order:* `{open_order}`") lines_detail = self._prepare_entry_details( - r['orders'], r['base_currency'], r['is_open']) + r['orders'], r['quote_currency'], r['is_open']) lines.extend(lines_detail if lines_detail else "") # Filter empty lines using list-comprehension diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e421b6fe5..f4a2f6099 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -52,7 +52,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', - 'base_currency': 'BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'open_date': ANY, 'open_timestamp': ANY, 'is_open': ANY, @@ -135,7 +136,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', - 'base_currency': 'BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'open_date': ANY, 'open_timestamp': ANY, 'is_open': ANY, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 3e1710c8e..76cef0df0 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -931,6 +931,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'open_order': None, 'open_rate': 0.123, 'pair': 'ETH/BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'stake_amount': 0.001, 'stop_loss_abs': ANY, 'stop_loss_pct': ANY, @@ -1097,7 +1099,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): # Test creating trade fbuy_mock = MagicMock(return_value=Trade( - pair='ETH/ETH', + pair='ETH/BTC', amount=1, amount_requested=1, exchange='binance', @@ -1130,7 +1132,9 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): 'open_date': ANY, 'open_timestamp': ANY, 'open_rate': 0.245441, - 'pair': 'ETH/ETH', + 'pair': 'ETH/BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'stake_amount': 1, 'stop_loss_abs': None, 'stop_loss_pct': None, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f104e7153..da853799b 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -184,7 +184,8 @@ def test_telegram_status(default_conf, update, mocker) -> None: _rpc_trade_status=MagicMock(return_value=[{ 'trade_id': 1, 'pair': 'ETH/BTC', - 'base_currency': 'BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'open_date': arrow.utcnow(), 'close_date': None, 'open_rate': 1.099e-05, diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 8ba8764e0..d30d33d3b 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1561,6 +1561,8 @@ def test_to_json(fee): assert result == {'trade_id': None, 'pair': 'ADA/USDT', + 'base_currency': 'ADA', + 'quote_currency': 'USDT', 'is_open': None, 'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"), 'open_timestamp': int(trade.open_date.timestamp() * 1000), @@ -1637,6 +1639,8 @@ def test_to_json(fee): assert result == {'trade_id': None, 'pair': 'XRP/BTC', + 'base_currency': 'XRP', + 'quote_currency': 'BTC', 'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"), 'open_timestamp': int(trade.open_date.timestamp() * 1000), 'close_date': trade.close_date.strftime("%Y-%m-%d %H:%M:%S"),