freqtrade_origin/freqtrade/persistence/key_value_store.py

210 lines
6.6 KiB
Python
Raw Normal View History

2023-04-08 08:02:38 +00:00
from datetime import datetime, timezone
from enum import Enum
from typing import ClassVar
2023-04-08 08:02:38 +00:00
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
from freqtrade.persistence.base import ModelBase, SessionType
ValueTypes = str | datetime | float | int
2023-04-08 08:02:38 +00:00
class ValueTypesEnum(str, Enum):
2024-05-12 14:48:11 +00:00
STRING = "str"
DATETIME = "datetime"
FLOAT = "float"
INT = "int"
2023-04-08 08:02:38 +00:00
2023-04-08 14:28:50 +00:00
class KeyStoreKeys(str, Enum):
2024-05-12 14:48:11 +00:00
BOT_START_TIME = "bot_start_time"
STARTUP_TIME = "startup_time"
2023-04-08 14:28:50 +00:00
2023-04-08 08:02:38 +00:00
class _KeyValueStoreModel(ModelBase):
"""
Pair Locks database model.
"""
2024-05-12 14:48:11 +00:00
__tablename__ = "KeyValueStore"
2023-04-08 08:02:38 +00:00
session: ClassVar[SessionType]
id: Mapped[int] = mapped_column(primary_key=True)
2023-04-08 14:28:50 +00:00
key: Mapped[KeyStoreKeys] = mapped_column(String(25), nullable=False, index=True)
2023-04-08 08:02:38 +00:00
2023-04-08 14:28:50 +00:00
value_type: Mapped[ValueTypesEnum] = mapped_column(String(20), nullable=False)
2023-04-08 08:02:38 +00:00
string_value: Mapped[str | None] = mapped_column(String(255), nullable=True)
datetime_value: Mapped[datetime | None]
float_value: Mapped[float | None]
int_value: Mapped[int | None]
2023-04-08 08:02:38 +00:00
class KeyValueStore:
2023-04-08 08:09:31 +00:00
"""
Generic bot-wide, persistent key-value store
Can be used to store generic values, e.g. very first bot startup time.
Supports the types str, datetime, float and int.
"""
2023-04-08 08:02:38 +00:00
@staticmethod
2023-04-08 14:28:50 +00:00
def store_value(key: KeyStoreKeys, value: ValueTypes) -> None:
2023-04-08 08:02:38 +00:00
"""
Store the given value for the given key.
2023-04-08 08:09:31 +00:00
:param key: Key to store the value for - can be used in get-value to retrieve the key
:param value: Value to store - can be str, datetime, float or int
2023-04-08 08:02:38 +00:00
"""
2024-05-12 14:48:11 +00:00
kv = (
_KeyValueStoreModel.session.query(_KeyValueStoreModel)
.filter(_KeyValueStoreModel.key == key)
.first()
)
2023-04-08 08:02:38 +00:00
if kv is None:
kv = _KeyValueStoreModel(key=key)
if isinstance(value, str):
kv.value_type = ValueTypesEnum.STRING
kv.string_value = value
elif isinstance(value, datetime):
kv.value_type = ValueTypesEnum.DATETIME
kv.datetime_value = value
elif isinstance(value, float):
kv.value_type = ValueTypesEnum.FLOAT
kv.float_value = value
elif isinstance(value, int):
kv.value_type = ValueTypesEnum.INT
kv.int_value = value
else:
2024-05-12 14:48:11 +00:00
raise ValueError(f"Unknown value type {kv.value_type}")
2023-04-08 08:02:38 +00:00
_KeyValueStoreModel.session.add(kv)
_KeyValueStoreModel.session.commit()
@staticmethod
2023-04-08 14:28:50 +00:00
def delete_value(key: KeyStoreKeys) -> None:
2023-04-08 08:02:38 +00:00
"""
Delete the value for the given key.
2023-04-08 08:09:31 +00:00
:param key: Key to delete the value for
2023-04-08 08:02:38 +00:00
"""
2024-05-12 14:48:11 +00:00
kv = (
_KeyValueStoreModel.session.query(_KeyValueStoreModel)
.filter(_KeyValueStoreModel.key == key)
.first()
)
2023-04-08 08:02:38 +00:00
if kv is not None:
_KeyValueStoreModel.session.delete(kv)
_KeyValueStoreModel.session.commit()
2023-04-08 12:30:27 +00:00
2023-04-08 14:23:55 +00:00
@staticmethod
def get_value(key: KeyStoreKeys) -> ValueTypes | None:
2023-04-08 14:23:55 +00:00
"""
Get the value for the given key.
:param key: Key to get the value for
"""
2024-05-12 14:48:11 +00:00
kv = (
_KeyValueStoreModel.session.query(_KeyValueStoreModel)
.filter(_KeyValueStoreModel.key == key)
.first()
)
2023-04-08 14:23:55 +00:00
if kv is None:
return None
if kv.value_type == ValueTypesEnum.STRING:
return kv.string_value
if kv.value_type == ValueTypesEnum.DATETIME and kv.datetime_value is not None:
return kv.datetime_value.replace(tzinfo=timezone.utc)
if kv.value_type == ValueTypesEnum.FLOAT:
return kv.float_value
if kv.value_type == ValueTypesEnum.INT:
return kv.int_value
# This should never happen unless someone messed with the database manually
2024-05-12 14:48:11 +00:00
raise ValueError(f"Unknown value type {kv.value_type}") # pragma: no cover
2023-04-08 14:23:55 +00:00
@staticmethod
def get_string_value(key: KeyStoreKeys) -> str | None:
2023-04-08 14:23:55 +00:00
"""
Get the value for the given key.
:param key: Key to get the value for
"""
2024-05-12 14:48:11 +00:00
kv = (
_KeyValueStoreModel.session.query(_KeyValueStoreModel)
.filter(
_KeyValueStoreModel.key == key,
_KeyValueStoreModel.value_type == ValueTypesEnum.STRING,
)
.first()
)
2023-04-08 14:23:55 +00:00
if kv is None:
return None
return kv.string_value
@staticmethod
def get_datetime_value(key: KeyStoreKeys) -> datetime | None:
2023-04-08 14:23:55 +00:00
"""
Get the value for the given key.
:param key: Key to get the value for
"""
2024-05-12 14:48:11 +00:00
kv = (
_KeyValueStoreModel.session.query(_KeyValueStoreModel)
.filter(
_KeyValueStoreModel.key == key,
_KeyValueStoreModel.value_type == ValueTypesEnum.DATETIME,
)
.first()
)
2023-04-08 14:23:55 +00:00
if kv is None or kv.datetime_value is None:
return None
return kv.datetime_value.replace(tzinfo=timezone.utc)
@staticmethod
def get_float_value(key: KeyStoreKeys) -> float | None:
2023-04-08 14:23:55 +00:00
"""
Get the value for the given key.
:param key: Key to get the value for
"""
2024-05-12 14:48:11 +00:00
kv = (
_KeyValueStoreModel.session.query(_KeyValueStoreModel)
.filter(
_KeyValueStoreModel.key == key,
_KeyValueStoreModel.value_type == ValueTypesEnum.FLOAT,
)
.first()
)
2023-04-08 14:23:55 +00:00
if kv is None:
return None
return kv.float_value
@staticmethod
def get_int_value(key: KeyStoreKeys) -> int | None:
2023-04-08 14:23:55 +00:00
"""
Get the value for the given key.
:param key: Key to get the value for
"""
2024-05-12 14:48:11 +00:00
kv = (
_KeyValueStoreModel.session.query(_KeyValueStoreModel)
.filter(
_KeyValueStoreModel.key == key, _KeyValueStoreModel.value_type == ValueTypesEnum.INT
)
.first()
)
2023-04-08 14:23:55 +00:00
if kv is None:
return None
return kv.int_value
2023-04-08 12:30:27 +00:00
def set_startup_time():
"""
sets bot_start_time to the first trade open date - or "now" on new databases.
sets startup_time to "now"
"""
2024-05-12 14:48:11 +00:00
st = KeyValueStore.get_value("bot_start_time")
2023-04-08 12:30:27 +00:00
if st is None:
from freqtrade.persistence import Trade
2024-05-12 14:48:11 +00:00
2023-04-08 12:30:27 +00:00
t = Trade.session.query(Trade).order_by(Trade.open_date.asc()).first()
if t is not None:
2024-05-12 14:48:11 +00:00
KeyValueStore.store_value("bot_start_time", t.open_date_utc)
2023-04-08 12:30:27 +00:00
else:
2024-05-12 14:48:11 +00:00
KeyValueStore.store_value("bot_start_time", datetime.now(timezone.utc))
KeyValueStore.store_value("startup_time", datetime.now(timezone.utc))