Files
aiomql/src/aiomql/core/config.py
T

170 lines
6.1 KiB
Python
Raw Normal View History

2023-10-11 09:49:06 +01:00
import os
from pathlib import Path
2024-05-05 00:08:57 +01:00
from typing import Iterator, Literal, TypeVar
2023-10-11 09:49:06 +01:00
import json
from logging import getLogger
2024-01-22 22:48:35 +01:00
from .task_queue import TaskQueue
2023-10-11 09:49:06 +01:00
logger = getLogger(__name__)
2024-05-05 00:08:57 +01:00
Bot = TypeVar("Bot")
2024-09-09 06:10:44 +01:00
TestData = TypeVar("TestData")
2023-10-11 09:49:06 +01:00
class Config:
"""A class for handling configuration settings for the aiomql package.
Attributes:
record_trades (bool): Whether to keep record of trades or not.
2024-05-05 00:08:57 +01:00
trade_record_mode: How to save trade, json or csv. Defaults to json
2023-10-11 09:49:06 +01:00
filename (str): Name of the config file
records_dir (str): Path to the directory where trade records are saved
login (int): Trading account number
password (str): Trading account password
server (str): Broker server
path (str): Path to terminal file
timeout (int): Timeout for terminal connection
2024-01-22 22:48:35 +01:00
state (dict): A global state dictionary for storing data across the framework
2024-09-02 05:59:00 +01:00
root (str): Root directory of the project
2023-10-11 09:49:06 +01:00
Notes:
By default, the config class looks for a file named aiomql.json.
2024-01-22 22:48:35 +01:00
You can change this by passing the filename and/or the config_dir keyword argument(s) to the constructor
or the load_config method.
2023-10-11 09:49:06 +01:00
By passing reload=True to the load_config method, you can reload and search again for the config file.
"""
2024-09-02 05:59:00 +01:00
login: int
trade_record_mode: Literal['csv', 'json']
password: str
server: str
path: str | Path
timeout: int
filename: str
state: dict
2024-05-05 00:08:57 +01:00
root: Path
2024-09-02 05:59:00 +01:00
record_trades: bool
2024-05-05 00:08:57 +01:00
records_dir: Path
2024-09-02 05:59:00 +01:00
records_dir_name: str
2024-09-09 06:10:44 +01:00
compress_test_data: bool
2024-09-02 05:59:00 +01:00
test_data_dir: Path
test_data_dir_name: str
task_queue: TaskQueue
2024-09-09 06:10:44 +01:00
_test_data: TestData
2024-09-02 05:59:00 +01:00
bot: Bot
2024-01-22 22:48:35 +01:00
_instance: 'Config'
2024-09-02 05:59:00 +01:00
mode: Literal['backtest', 'live']
use_terminal_for_backtesting: bool
2024-09-09 06:10:44 +01:00
test_data_file: str
2024-09-02 05:59:00 +01:00
_defaults = {"timeout": 60000, "record_trades": True, "trade_record_mode": "csv", "mode": "live",
'filename': "aiomql.json", "records_dir_name": "trade_records", "test_data_dir_name": "test_data",
2024-09-09 06:10:44 +01:00
"use_terminal_for_backtesting": True, 'path': '', 'login': 0, 'password': '', 'server': '',
"compress_test_data": False, 'test_data_file': ''}
2023-10-11 09:49:06 +01:00
def __new__(cls, *args, **kwargs):
2024-01-01 05:25:41 +01:00
if not hasattr(cls, "_instance"):
2023-10-11 09:49:06 +01:00
cls._instance = super().__new__(cls)
2024-09-02 05:59:00 +01:00
cls._instance.state = {}
cls._instance.task_queue = TaskQueue()
cls._instance.set_attributes(**cls._defaults)
2024-09-09 06:10:44 +01:00
cls._instance._test_data = None
2024-09-02 05:59:00 +01:00
cls._instance.load_config(**kwargs)
2023-10-11 09:49:06 +01:00
return cls._instance
2024-01-01 05:25:41 +01:00
2023-10-11 09:49:06 +01:00
def __init__(self, **kwargs):
2024-09-02 05:59:00 +01:00
self.set_attributes(**kwargs)
2023-10-11 09:49:06 +01:00
2024-09-09 06:10:44 +01:00
@property
def test_data(self):
return self._test_data
@test_data.setter
def test_data(self, value: TestData):
self._test_data = value
2024-08-20 06:22:51 +01:00
def set_attributes(self, **kwargs):
"""Set keyword arguments as object attributes
Keyword Args:
**kwargs: Object attributes and values as keyword arguments
"""
[setattr(self, key, value) for key, value in kwargs.items()]
2023-10-11 09:49:06 +01:00
@staticmethod
2024-01-22 22:48:35 +01:00
def walk_to_root(path: str | Path) -> Iterator[str]:
2023-10-11 09:49:06 +01:00
if not os.path.exists(path):
2024-01-01 05:25:41 +01:00
raise IOError("Starting path not found")
2023-10-11 09:49:06 +01:00
if os.path.isfile(path):
path = os.path.dirname(path)
2024-01-01 05:25:41 +01:00
2023-10-11 09:49:06 +01:00
last_dir = None
current_dir = os.path.abspath(path)
while last_dir != current_dir:
yield current_dir
parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
last_dir, current_dir = current_dir, parent_dir
2024-01-01 05:25:41 +01:00
2024-09-02 05:59:00 +01:00
def find_config_file(self):
2024-01-22 22:48:35 +01:00
try:
2024-09-02 05:59:00 +01:00
for dirname in self.walk_to_root(self.root):
2024-01-22 22:48:35 +01:00
check_path = os.path.join(dirname, self.filename)
if os.path.isfile(check_path):
return check_path
return None
except Exception as _:
return
2024-01-01 05:25:41 +01:00
2024-09-02 05:59:00 +01:00
def load_config(self, *, file: str | Path = None, filename: str = None, root: str | Path = None, **kwargs):
2024-02-12 21:09:28 +01:00
"""Load configuration settings from a file.
2024-05-05 00:08:57 +01:00
2024-09-02 05:59:00 +01:00
Keyword Args:
file (str | Path): The absolute path to the config file.
filename (str): The name of the file to load if file path is not specified. If not provided aiomql.json is used
root (str): The root directory of the project.
kwargs: Additional keyword arguments to set as object attributes.
"""
if root is not None:
root = Path(root).resolve()
root.mkdir(parents=True, exist_ok=True) if not root.exists() else ...
self.root = root
else:
self.root = self.root if hasattr(self, 'root') else Path.cwd()
if file is not None:
file = Path(file).resolve()
if not file.exists():
self.filename = filename or self.filename
file = self.find_config_file()
else:
self.filename = file.name
else:
self.filename = filename or self.filename
file = self.find_config_file()
if file is None:
2024-01-01 05:25:41 +01:00
logger.warning("No Config File Found")
2024-09-02 05:59:00 +01:00
file_config = {}
2023-10-11 09:49:06 +01:00
else:
2024-01-01 05:25:41 +01:00
fh = open(file, mode="r")
2024-09-02 05:59:00 +01:00
file_config = json.load(fh)
2023-10-11 09:49:06 +01:00
fh.close()
2024-09-02 05:59:00 +01:00
data = file_config | kwargs
2024-08-20 06:22:51 +01:00
self.set_attributes(**data)
2024-09-02 05:59:00 +01:00
if self.record_trades and not hasattr(self, "records_dir"):
self.records_dir = self.root / self.records_dir_name
self.records_dir.mkdir(parents=True, exist_ok=True)
if self.mode == "backtest" and not hasattr(self, "test_data_dir"):
self.test_data_dir = self.root / self.test_data_dir_name
self.test_data_dir.mkdir(parents=True, exist_ok=True)
2023-10-11 09:49:06 +01:00
2024-01-18 02:26:27 +01:00
def account_info(self) -> dict[str, int | str]:
2023-10-11 09:49:06 +01:00
"""Returns Account login details as found in the config object if available
2024-01-01 05:25:41 +01:00
Returns:
dict: A dictionary of login details
2023-10-11 09:49:06 +01:00
"""
2024-09-02 05:59:00 +01:00
return {'login': self.login, 'password': self.password, 'server': self.server}