Rename persistant storage infrastructure.

This commit is contained in:
eSeR1805 2022-06-19 15:56:50 +03:00
parent 0009b987e4
commit 9fdb8b07ac
No known key found for this signature in database
GPG Key ID: BA53686259B46936
10 changed files with 123 additions and 117 deletions

View File

@ -43,7 +43,7 @@ class AwesomeStrategy(IStrategy):
## Storing information (Persistent) ## Storing information (Persistent)
Storing information can also be performed in a persistent manner. Freqtrade allows storing/retrieving user custom information associated with a specific trade. Storing information can also be performed in a persistent manner. Freqtrade allows storing/retrieving user custom information associated with a specific trade.
Using a trade object handle information can be stored using `trade_obj.set_kval(key='my_key', value=my_value)` and retrieved using `trade_obj.get_kvals(key='my_key')`. Using a trade object handle information can be stored using `trade_obj.set_custom_data(key='my_key', value=my_value)` and retrieved using `trade_obj.get_custom_data(key='my_key')`.
Each data entry is associated with a trade and a user supplied key (of type `string`). This means that this can only be used in callbacks that also provide a trade object handle. Each data entry is associated with a trade and a user supplied key (of type `string`). This means that this can only be used in callbacks that also provide a trade object handle.
For the data to be able to be stored within the database it must be serialized. This is done by converting it to a JSON formatted string. For the data to be able to be stored within the database it must be serialized. This is done by converting it to a JSON formatted string.
@ -57,12 +57,12 @@ class AwesomeStrategy(IStrategy):
for trade in Trade.get_open_order_trades(): for trade in Trade.get_open_order_trades():
fills = trade.select_filled_orders(trade.entry_side) fills = trade.select_filled_orders(trade.entry_side)
if trade.pair == 'ETH/USDT': if trade.pair == 'ETH/USDT':
trade_entry_type = trade.get_kvals(key='entry_type').kv_value trade_entry_type = trade.get_custom_data(key='entry_type').kv_value
if trade_entry_type is None: if trade_entry_type is None:
trade_entry_type = 'breakout' if 'entry_1' in trade.enter_tag else 'dip' trade_entry_type = 'breakout' if 'entry_1' in trade.enter_tag else 'dip'
elif fills > 1: elif fills > 1:
trade_entry_type = 'buy_up' trade_entry_type = 'buy_up'
trade.set_kval(key='entry_type', value=trade_entry_type) trade.set_custom_data(key='entry_type', value=trade_entry_type)
return super().bot_loop_start(**kwargs) return super().bot_loop_start(**kwargs)
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str, def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
@ -73,12 +73,12 @@ class AwesomeStrategy(IStrategy):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze() current_candle = dataframe.iloc[-1].squeeze()
# store information about entry adjustment # store information about entry adjustment
existing_count = trade.get_kvals(key='num_entry_adjustments').kv_value existing_count = trade.get_custom_data(key='num_entry_adjustments').kv_value
if not existing_count: if not existing_count:
existing_count = 1 existing_count = 1
else: else:
existing_count += 1 existing_count += 1
trade.set_kval(key='num_entry_adjustments', value=existing_count) trade.set_custom_data(key='num_entry_adjustments', value=existing_count)
# adjust order price # adjust order price
return current_candle['sma_200'] return current_candle['sma_200']
@ -88,8 +88,8 @@ class AwesomeStrategy(IStrategy):
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs): def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
entry_adjustment_count = trade.get_kvals(key='num_entry_adjustments').kv_value entry_adjustment_count = trade.get_custom_data(key='num_entry_adjustments').kv_value
trade_entry_type = trade.get_kvals(key='entry_type').kv_value trade_entry_type = trade.get_custom_data(key='entry_type').kv_value
if entry_adjustment_count is None: if entry_adjustment_count is None:
if current_profit > 0.01 and (current_time - timedelta(minutes=100) > trade.open_date_utc): if current_profit > 0.01 and (current_time - timedelta(minutes=100) > trade.open_date_utc):
return True, 'exit_1' return True, 'exit_1'

View File

@ -30,7 +30,7 @@ from freqtrade.optimize.bt_progress import BTProgress
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results,
store_backtest_signal_candles, store_backtest_signal_candles,
store_backtest_stats) store_backtest_stats)
from freqtrade.persistence import KeyValues, LocalTrade, Order, PairLocks, Trade from freqtrade.persistence import CustomDataWrapper, LocalTrade, Order, PairLocks, Trade
from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.plugins.protectionmanager import ProtectionManager
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
@ -151,7 +151,7 @@ class Backtesting:
LoggingMixin.show_output = True LoggingMixin.show_output = True
PairLocks.use_db = True PairLocks.use_db = True
Trade.use_db = True Trade.use_db = True
KeyValues.use_db = True # ??? CustomDataWrapper.use_db = True
def init_backtest_detail(self): def init_backtest_detail(self):
# Load detail timeframe if specified # Load detail timeframe if specified
@ -300,8 +300,8 @@ class Backtesting:
Trade.use_db = False Trade.use_db = False
PairLocks.reset_locks() PairLocks.reset_locks()
Trade.reset_trades() Trade.reset_trades()
KeyValues.use_db = False CustomDataWrapper.use_db = False
KeyValues.reset_keyvalues() CustomDataWrapper.reset_custom_data()
self.rejected_trades = 0 self.rejected_trades = 0
self.timedout_entry_orders = 0 self.timedout_entry_orders = 0
self.timedout_exit_orders = 0 self.timedout_exit_orders = 0

View File

@ -1,6 +1,6 @@
# flake8: noqa: F401 # flake8: noqa: F401
from freqtrade.persistence.keyvalue_middleware import KeyValues from freqtrade.persistence.keyvalue_middleware import CustomDataWrapper
from freqtrade.persistence.models import cleanup_db, init_db from freqtrade.persistence.models import cleanup_db, init_db
from freqtrade.persistence.pairlock_middleware import PairLocks from freqtrade.persistence.pairlock_middleware import PairLocks
from freqtrade.persistence.trade_model import LocalTrade, Order, Trade from freqtrade.persistence.trade_model import LocalTrade, Order, Trade

View File

@ -8,28 +8,28 @@ from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.persistence.base import _DECL_BASE from freqtrade.persistence.base import _DECL_BASE
class KeyValue(_DECL_BASE): class CustomData(_DECL_BASE):
""" """
KeyValue database model CustomData database model
Keeps records of metadata as key/value store Keeps records of metadata as key/value store
for trades or global persistant values for trades or global persistant values
One to many relationship with Trades: One to many relationship with Trades:
- One trade can have many metadata entries - One trade can have many metadata entries
- One metadata entry can only be associated with one Trade - One metadata entry can only be associated with one Trade
""" """
__tablename__ = 'keyvalue' __tablename__ = 'trade_custom_data'
# Uniqueness should be ensured over pair, order_id # Uniqueness should be ensured over pair, order_id
# its likely that order_id is unique per Pair on some exchanges. # its likely that order_id is unique per Pair on some exchanges.
__table_args__ = (UniqueConstraint('ft_trade_id', 'kv_key', name="_trade_id_kv_key"),) __table_args__ = (UniqueConstraint('ft_trade_id', 'cd_key', name="_trade_id_cd_key"),)
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
ft_trade_id = Column(Integer, ForeignKey('trades.id'), index=True, default=0) ft_trade_id = Column(Integer, ForeignKey('trades.id'), index=True, default=0)
trade = relationship("Trade", back_populates="keyvalues") trade = relationship("Trade", back_populates="custom_data")
kv_key = Column(String(255), nullable=False) cd_key = Column(String(255), nullable=False)
kv_type = Column(String(25), nullable=False) cd_type = Column(String(25), nullable=False)
kv_value = Column(Text, nullable=False) cd_value = Column(Text, nullable=False)
created_at = Column(DateTime, nullable=False, default=datetime.utcnow) created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
updated_at = Column(DateTime, nullable=True) updated_at = Column(DateTime, nullable=True)
@ -38,20 +38,20 @@ class KeyValue(_DECL_BASE):
if self.created_at is not None else None) if self.created_at is not None else None)
update_time = (self.updated_at.strftime(DATETIME_PRINT_FORMAT) update_time = (self.updated_at.strftime(DATETIME_PRINT_FORMAT)
if self.updated_at is not None else None) if self.updated_at is not None else None)
return (f'KeyValue(id={self.id}, key={self.kv_key}, type={self.kv_type}, ' + return (f'CustomData(id={self.id}, key={self.cd_key}, type={self.cd_type}, ' +
f'value={self.kv_value}, trade_id={self.ft_trade_id}, created={create_time}, ' + f'value={self.cd_value}, trade_id={self.ft_trade_id}, created={create_time}, ' +
f'updated={update_time})') f'updated={update_time})')
@staticmethod @staticmethod
def query_kv(key: Optional[str] = None, trade_id: Optional[int] = None) -> Query: def query_cd(key: Optional[str] = None, trade_id: Optional[int] = None) -> Query:
""" """
Get all keyvalues, if trade_id is not specified Get all CustomData, if trade_id is not specified
return will be for generic values not tied to a trade return will be for generic values not tied to a trade
:param trade_id: id of the Trade :param trade_id: id of the Trade
""" """
filters = [] filters = []
filters.append(KeyValue.ft_trade_id == trade_id if trade_id is not None else 0) filters.append(CustomData.ft_trade_id == trade_id if trade_id is not None else 0)
if key is not None: if key is not None:
filters.append(KeyValue.kv_key.ilike(key)) filters.append(CustomData.cd_key.ilike(key))
return KeyValue.query.filter(*filters) return CustomData.query.filter(*filters)

View File

@ -3,57 +3,63 @@ import logging
from datetime import datetime from datetime import datetime
from typing import Any, List, Optional from typing import Any, List, Optional
from freqtrade.persistence.keyvalue import KeyValue from freqtrade.persistence.keyvalue import CustomData
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class KeyValues(): class CustomDataWrapper():
""" """
KeyValues middleware class CustomData middleware class
Abstracts the database layer away so it becomes optional - which will be necessary to support Abstracts the database layer away so it becomes optional - which will be necessary to support
backtesting and hyperopt in the future. backtesting and hyperopt in the future.
""" """
use_db = True use_db = True
kvals: List[KeyValue] = [] custom_data: List[CustomData] = []
unserialized_types = ['bool', 'float', 'int', 'str'] unserialized_types = ['bool', 'float', 'int', 'str']
@staticmethod @staticmethod
def reset_keyvalues() -> None: def reset_custom_data() -> None:
""" """
Resets all key-value pairs. Only active for backtesting mode. Resets all key-value pairs. Only active for backtesting mode.
""" """
if not KeyValues.use_db: if not CustomDataWrapper.use_db:
KeyValues.kvals = [] CustomDataWrapper.custom_data = []
@staticmethod @staticmethod
def get_kval(key: Optional[str] = None, trade_id: Optional[int] = None) -> List[KeyValue]: def get_custom_data(key: Optional[str] = None,
trade_id: Optional[int] = None) -> List[CustomData]:
if trade_id is None: if trade_id is None:
trade_id = 0 trade_id = 0
if KeyValues.use_db: if CustomDataWrapper.use_db:
filtered_kvals = KeyValue.query_kv(trade_id=trade_id, key=key).all() filtered_custom_data = CustomData.query_cd(trade_id=trade_id, key=key).all()
for index, kval in enumerate(filtered_kvals): for index, data_entry in enumerate(filtered_custom_data):
if kval.kv_type not in KeyValues.unserialized_types: if data_entry.cd_type not in CustomDataWrapper.unserialized_types:
kval.kv_value = json.loads(kval.kv_value) data_entry.cd_value = json.loads(data_entry.cd_value)
filtered_kvals[index] = kval filtered_custom_data[index] = data_entry
return filtered_kvals return filtered_custom_data
else: else:
filtered_kvals = [kval for kval in KeyValues.kvals if (kval.ft_trade_id == trade_id)] filtered_custom_data = [
data_entry for data_entry in CustomDataWrapper.custom_data
if (data_entry.ft_trade_id == trade_id)
]
if key is not None: if key is not None:
filtered_kvals = [ filtered_custom_data = [
kval for kval in filtered_kvals if (kval.kv_key.casefold() == key.casefold())] data_entry for data_entry in filtered_custom_data
return filtered_kvals if (data_entry.cd_key.casefold() == key.casefold())
]
return filtered_custom_data
@staticmethod @staticmethod
def set_kval(key: str, value: Any, trade_id: Optional[int] = None) -> None: def set_custom_data(key: str, value: Any, trade_id: Optional[int] = None) -> None:
value_type = type(value).__name__ value_type = type(value).__name__
value_db = None value_db = None
if value_type not in KeyValues.unserialized_types: if value_type not in CustomDataWrapper.unserialized_types:
try: try:
value_db = json.dumps(value) value_db = json.dumps(value)
except TypeError as e: except TypeError as e:
@ -64,44 +70,44 @@ class KeyValues():
if trade_id is None: if trade_id is None:
trade_id = 0 trade_id = 0
kvals = KeyValues.get_kval(key=key, trade_id=trade_id) custom_data = CustomDataWrapper.get_custom_data(key=key, trade_id=trade_id)
if kvals: if custom_data:
kv = kvals[0] data_entry = custom_data[0]
kv.kv_value = value data_entry.cd_value = value
kv.updated_at = datetime.utcnow() data_entry.updated_at = datetime.utcnow()
else: else:
kv = KeyValue( data_entry = CustomData(
ft_trade_id=trade_id, ft_trade_id=trade_id,
kv_key=key, cd_key=key,
kv_type=value_type, cd_type=value_type,
kv_value=value, cd_value=value,
created_at=datetime.utcnow() created_at=datetime.utcnow()
) )
if KeyValues.use_db and value_db is not None: if CustomDataWrapper.use_db and value_db is not None:
kv.kv_value = value_db data_entry.cd_value = value_db
KeyValue.query.session.add(kv) CustomData.query.session.add(data_entry)
KeyValue.query.session.commit() CustomData.query.session.commit()
elif not KeyValues.use_db: elif not CustomDataWrapper.use_db:
kv_index = -1 cd_index = -1
for index, kval in enumerate(KeyValues.kvals): for index, data_entry in enumerate(CustomDataWrapper.custom_data):
if kval.ft_trade_id == trade_id and kval.kv_key == key: if data_entry.ft_trade_id == trade_id and data_entry.cd_key == key:
kv_index = index cd_index = index
break break
if kv_index >= 0: if cd_index >= 0:
kval.kv_type = value_type data_entry.cd_type = value_type
kval.value = value data_entry.value = value
kval.updated_at = datetime.utcnow() data_entry.updated_at = datetime.utcnow()
KeyValues.kvals[kv_index] = kval CustomDataWrapper.custom_data[cd_index] = data_entry
else: else:
KeyValues.kvals.append(kv) CustomDataWrapper.custom_data.append(data_entry)
@staticmethod @staticmethod
def get_all_kvals() -> List[KeyValue]: def get_all_custom_data() -> List[CustomData]:
if KeyValues.use_db: if CustomDataWrapper.use_db:
return KeyValue.query.all() return CustomData.query.all()
else: else:
return KeyValues.kvals return CustomDataWrapper.custom_data

View File

@ -10,7 +10,7 @@ from sqlalchemy.pool import StaticPool
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.persistence.base import _DECL_BASE from freqtrade.persistence.base import _DECL_BASE
from freqtrade.persistence.keyvalue import KeyValue from freqtrade.persistence.keyvalue import CustomData
from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.migrations import check_migrate
from freqtrade.persistence.pairlock import PairLock from freqtrade.persistence.pairlock import PairLock
from freqtrade.persistence.trade_model import Order, Trade from freqtrade.persistence.trade_model import Order, Trade
@ -58,8 +58,8 @@ def init_db(db_url: str) -> None:
Trade.query = Trade._session.query_property() Trade.query = Trade._session.query_property()
Order.query = Trade._session.query_property() Order.query = Trade._session.query_property()
PairLock.query = Trade._session.query_property() PairLock.query = Trade._session.query_property()
KeyValue._session = scoped_session(sessionmaker(bind=engine, autoflush=True)) CustomData._session = scoped_session(sessionmaker(bind=engine, autoflush=True))
KeyValue.query = KeyValue._session.query_property() CustomData.query = CustomData._session.query_property()
previous_tables = inspect(engine).get_table_names() previous_tables = inspect(engine).get_table_names()
_DECL_BASE.metadata.create_all(engine) _DECL_BASE.metadata.create_all(engine)

View File

@ -15,8 +15,8 @@ from freqtrade.enums import ExitType, TradingMode
from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.leverage import interest from freqtrade.leverage import interest
from freqtrade.persistence.base import _DECL_BASE from freqtrade.persistence.base import _DECL_BASE
from freqtrade.persistence.keyvalue import KeyValue from freqtrade.persistence.keyvalue import CustomData
from freqtrade.persistence.keyvalue_middleware import KeyValues from freqtrade.persistence.keyvalue_middleware import CustomDataWrapper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -240,7 +240,7 @@ class LocalTrade():
id: int = 0 id: int = 0
orders: List[Order] = [] orders: List[Order] = []
keyvalues: List[KeyValue] = [] custom_data: List[CustomData] = []
exchange: str = '' exchange: str = ''
pair: str = '' pair: str = ''
@ -880,11 +880,11 @@ class LocalTrade():
or (o.ft_is_open is True and o.status is not None) or (o.ft_is_open is True and o.status is not None)
] ]
def set_kval(self, key: str, value: Any) -> None: def set_custom_data(self, key: str, value: Any) -> None:
KeyValues.set_kval(key=key, value=value, trade_id=self.id) CustomDataWrapper.set_custom_data(key=key, value=value, trade_id=self.id)
def get_kvals(self, key: Optional[str]) -> List[KeyValue]: def get_custom_data(self, key: Optional[str]) -> List[CustomData]:
return KeyValues.get_kval(key=key, trade_id=self.id) return CustomDataWrapper.get_custom_data(key=key, trade_id=self.id)
@property @property
def nr_of_successful_entries(self) -> int: def nr_of_successful_entries(self) -> int:
@ -1016,7 +1016,7 @@ class Trade(_DECL_BASE, LocalTrade):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan", lazy="joined") orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan", lazy="joined")
keyvalues = relationship("KeyValue", order_by="KeyValue.id", cascade="all, delete-orphan") custom_data = relationship("CustomData", order_by="CustomData.id", cascade="all, delete-orphan")
exchange = Column(String(25), nullable=False) exchange = Column(String(25), nullable=False)
pair = Column(String(25), nullable=False, index=True) pair = Column(String(25), nullable=False, index=True)
@ -1090,9 +1090,9 @@ class Trade(_DECL_BASE, LocalTrade):
Trade.query.session.delete(self) Trade.query.session.delete(self)
Trade.commit() Trade.commit()
for kval in self.keyvalues: for entry in self.custom_data:
KeyValue.query.session.delete(kval) CustomData.query.session.delete(entry)
KeyValue.query.session.commit() CustomData.query.session.commit()
@staticmethod @staticmethod
def commit(): def commit():
@ -1367,11 +1367,11 @@ class Trade(_DECL_BASE, LocalTrade):
.order_by(desc('profit_sum')).first() .order_by(desc('profit_sum')).first()
return best_pair return best_pair
def set_kval(self, key: str, value: Any) -> None: def set_custom_data(self, key: str, value: Any) -> None:
super().set_kval(key=key, value=value) super().set_custom_data(key=key, value=value)
def get_kvals(self, key: Optional[str]) -> List[KeyValue]: def get_custom_data(self, key: Optional[str]) -> List[CustomData]:
return super().get_kvals(key=key) return super().get_custom_data(key=key)
@staticmethod @staticmethod
def get_trading_volume(start_date: datetime = datetime.fromtimestamp(0)) -> float: def get_trading_volume(start_date: datetime = datetime.fromtimestamp(0)) -> float:

View File

@ -805,24 +805,24 @@ class RPC:
'cancel_order_count': c_count, 'cancel_order_count': c_count,
} }
def _rpc_list_kvals(self, trade_id: int, key: Optional[str]) -> List[Dict[str, Any]]: def _rpc_list_custom_data(self, trade_id: int, key: Optional[str]) -> List[Dict[str, Any]]:
# Query for trade # Query for trade
trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first() trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first()
if trade is None: if trade is None:
return [] return []
# Query keyvals # Query custom_data
keyvals = trade.get_kvals(key=key) custom_data = trade.get_custom_data(key=key)
return [ return [
{ {
'id': kval.id, 'id': data_entry.id,
'ft_trade_id': kval.ft_trade_id, 'ft_trade_id': data_entry.ft_trade_id,
'kv_key': kval.kv_key, 'cd_key': data_entry.cd_key,
'kv_type': kval.kv_type, 'cd_type': data_entry.cd_type,
'kv_value': kval.kv_value, 'cd_value': data_entry.cd_value,
'created_at': kval.created_at, 'created_at': data_entry.created_at,
'updated_at': kval.updated_at 'updated_at': data_entry.updated_at
} }
for kval in keyvals for data_entry in custom_data
] ]
def _rpc_performance(self) -> List[Dict[str, Any]]: def _rpc_performance(self) -> List[Dict[str, Any]]:

View File

@ -192,7 +192,7 @@ class Telegram(RPCHandler):
CommandHandler('health', self._health), CommandHandler('health', self._health),
CommandHandler('help', self._help), CommandHandler('help', self._help),
CommandHandler('version', self._version), CommandHandler('version', self._version),
CommandHandler('list_kvals', self._list_kvals), CommandHandler('list_custom_data', self._list_custom_data),
] ]
callbacks = [ callbacks = [
CallbackQueryHandler(self._status_table, pattern='update_status_table'), CallbackQueryHandler(self._status_table, pattern='update_status_table'),
@ -1453,7 +1453,7 @@ class Telegram(RPCHandler):
"Avg. holding durationsfor buys and sells.`\n" "Avg. holding durationsfor buys and sells.`\n"
"*/help:* `This help message`\n" "*/help:* `This help message`\n"
"*/version:* `Show version`\n" "*/version:* `Show version`\n"
"*/list_kvals <trade_id> <key>:* `List key-value for Trade ID and Key combo.`\n" "*/list_custom_data <trade_id> <key>:* `List custom_data for Trade ID & Key combo.`\n"
"`If no Key is supplied it will list all key-value pairs found for that Trade ID.`" "`If no Key is supplied it will list all key-value pairs found for that Trade ID.`"
) )
@ -1535,10 +1535,10 @@ class Telegram(RPCHandler):
) )
@authorized_only @authorized_only
def _list_kvals(self, update: Update, context: CallbackContext) -> None: def _list_custom_data(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /list_kvals <id> <key>. Handler for /list_custom_data <id> <key>.
List keyvalues for specified trade (and key if supplied). List custom_data for specified trade (and key if supplied).
:param bot: telegram bot :param bot: telegram bot
:param update: message update :param update: message update
:return: None :return: None
@ -1549,17 +1549,17 @@ class Telegram(RPCHandler):
trade_id = int(context.args[0]) trade_id = int(context.args[0])
key = None if len(context.args) < 2 else str(context.args[1]) key = None if len(context.args) < 2 else str(context.args[1])
results = self._rpc._rpc_list_kvals(trade_id, key) results = self._rpc._rpc_list_custom_data(trade_id, key)
messages = [] messages = []
if len(results) > 0: if len(results) > 0:
messages = ['Found key-value pair' + 's: \n' if key is None else ': \n'] messages = ['Found key-value pair' + 's: \n' if key is None else ': \n']
for result in results: for result in results:
lines = [ lines = [
f"*Key:* `{result['kv_key']}`", f"*Key:* `{result['cd_key']}`",
f"*ID:* `{result['id']}`", f"*ID:* `{result['id']}`",
f"*Trade ID:* `{result['ft_trade_id']}`", f"*Trade ID:* `{result['ft_trade_id']}`",
f"*Type:* `{result['kv_type']}`", f"*Type:* `{result['cd_type']}`",
f"*Value:* `{result['kv_value']}`", f"*Value:* `{result['cd_value']}`",
f"*Create Date:* `{result['created_at']}`", f"*Create Date:* `{result['created_at']}`",
f"*Update Date:* `{result['updated_at']}`" f"*Update Date:* `{result['updated_at']}`"
] ]

View File

@ -103,7 +103,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
"['count'], ['locks'], ['unlock', 'delete_locks'], " "['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
"['stopbuy'], ['whitelist'], ['blacklist'], ['blacklist_delete', 'bl_delete'], " "['stopbuy'], ['whitelist'], ['blacklist'], ['blacklist_delete', 'bl_delete'], "
"['logs'], ['edge'], ['health'], ['help'], ['version'], ['list_kvals']" "['logs'], ['edge'], ['health'], ['help'], ['version'], ['list_custom_data']"
"]") "]")
assert log_has(message_str, caplog) assert log_has(message_str, caplog)