From 626658d68ac771353b424d50a33e7dd0de6a9054 Mon Sep 17 00:00:00 2001 From: Victor Joseph Date: Sun, 29 Jun 2025 11:48:28 +0100 Subject: [PATCH 1/2] Add visualization with mplfinance. Add config attribute to Candles class. Add plots_dir property and plots_dir_name attribute to the Config class. Update documentation for Candle, Candles and Config classes. --- .gitignore | 3 +- docs/core/config.md | 1 + docs/lib/candle.md | 91 +++++++++++++++------- docs/lib/ticks.md | 4 +- requirements.txt | 158 -------------------------------------- src/aiomql/core/config.py | 14 +++- src/aiomql/lib/candle.py | 39 ++++++++++ src/aiomql/lib/ticks.py | 1 + 8 files changed, 121 insertions(+), 190 deletions(-) delete mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 10839b0..30ff9ef 100644 --- a/.gitignore +++ b/.gitignore @@ -78,7 +78,6 @@ aiomql.json # development terminals/ -*.pkl backtesting/ trade_records/ - +plots/ diff --git a/docs/core/config.md b/docs/core/config.md index a0d3636..832cc7f 100644 --- a/docs/core/config.md +++ b/docs/core/config.md @@ -29,6 +29,7 @@ A single instance of this class is created and used per bot instance. | `root` | `Path` | The root directory of the project | | `record_trades` | `bool` | To record trades or not. Default is True | | `records_dir` | `Path` | The directory to store trade records, relative to the root directory | +| `plots_dir` | `Path` | Save chart plots as images | | `backtest_dir` | `Path` | The directory to store backtest results, relative to the root directory | | `task_queue` | `TaskQueue` | The TaskQueue object for handling background tasks | | `_backtest_engine` | `BackTestEngine` | The backtest engine object | diff --git a/docs/lib/candle.md b/docs/lib/candle.md index 80bb15c..bca6ed9 100644 --- a/docs/lib/candle.md +++ b/docs/lib/candle.md @@ -14,6 +14,8 @@ Candle and Candles classes for handling bars from the MetaTrader 5 terminal. - [ta_lib](#candles.ta_lib) - [data](#candles.data) - [rename](#candles.rename) + - [plot](#candles.plot) + - [make_subplot](#candles.make_subplot) ### Candle @@ -22,18 +24,21 @@ class Candle ``` A class representing bars from the MetaTrader 5 terminal as a customized class analogous to Japanese Candlesticks. You can subclass this class for added customization. + ### Attributes -| Name | Type | Description | -|---------------|---------|-------------------------------------------------------------------------| -| `time` | `int` | Period start time | -| `open` | `int` | Open price | -| `high` | `float` | The highest price of the period | -| `low` | `float` | The lowest price of the period | -| `close` | `float` | Close price | -| `tick_volume` | `float` | Tick volume | -| `real_volume` | `float` | Trade volume | -| `spread` | `float` | Spread | -| `Index` | `int` | Custom attribute representing the position of the candle in a sequence. | + +| Name | Type | Description | +|---------------|-------------|-------------------------------------------------------------------------| +| `time` | `int` | Period start time | +| `open` | `int` | Open price | +| `high` | `float` | The highest price of the period | +| `low` | `float` | The lowest price of the period | +| `close` | `float` | Close price | +| `tick_volume` | `float` | Tick volume | +| `real_volume` | `float` | Trade volume | +| `spread` | `float` | Spread | +| `Index` | `int` | Custom attribute representing the position of the candle in a sequence. | +| `index` | `Timestamp` | Index of the object in the DataFrame, as a timestamp. | ### \_\_init\_\_ @@ -118,24 +123,25 @@ object for accessing the columns of the underlying data attribute. The attributes of this class vary depending on the columns of underlying **data** attribute. i.e. each column of the **data** attribute is an attribute of the class. -| Name | Type | Description | -|---------------|-----------------|-------------------------------------------------------------------| -| `data` | `DataFrame` | The pandas DataFrame containing the data. | -| `Index` | `Series['int']` | A pandas Series of the indexes of all candles in the object | -| `time` | `Series['int']` | A pandas Series of the time of all candles in the object | -| `open` | `Series[float]` | A pandas Series of the opening price of all candles in the object | -| `high` | `Series[float]` | A pandas Series of the high price of all candles in the object | -| `low` | `Series[float]` | A pandas Series of the low price of all candles in the object | -| `close` | `Series[float]` | A pandas Series of the closing price of all candles in the object | -| `tick_volume` | `Series[float]` | A pandas Series of the tick volume of all candles in the object | -| `real_volume` | `Series[float]` | A pandas Series of the real volume of all candles in the object | -| `spread` | `Series[float]` | A pandas Series of the spread of all candles in the object | -| `timeframe` | `TimeFrame` | The timeframe of the candles in the object | -| `Candle` | `Type[Candle]` | The Candle class for representing the candles in the object. | -| `data` | `DataFrame` | A pandas DataFrame of all candles in the object. | +| Name | Type | Description | +|---------------|-----------------------|-------------------------------------------------------------------| +| `data` | `DataFrame` | The pandas DataFrame containing the data. | +| `Index` | `Series['int']` | A pandas Series of the indexes of all candles in the object | +| `index` | `Series['Timestamp']` | DatetimeIndex of the underlying DataFrame object. | +| `time` | `Series['int']` | A pandas Series of the time of all candles in the object | +| `open` | `Series[float]` | A pandas Series of the opening price of all candles in the object | +| `high` | `Series[float]` | A pandas Series of the high price of all candles in the object | +| `low` | `Series[float]` | A pandas Series of the low price of all candles in the object | +| `close` | `Series[float]` | A pandas Series of the closing price of all candles in the object | +| `tick_volume` | `Series[float]` | A pandas Series of the tick volume of all candles in the object | +| `real_volume` | `Series[float]` | A pandas Series of the real volume of all candles in the object | +| `spread` | `Series[float]` | A pandas Series of the spread of all candles in the object | +| `timeframe` | `TimeFrame` | The timeframe of the candles in the object | +| `Candle` | `Type[Candle]` | The Candle class for representing the candles in the object. | +| `data` | `DataFrame` | A pandas DataFrame of all candles in the object. | #### Notes -When subclassing this class make sure the Candle attribute is set to your desired candle class. +When subclassing this class, make sure the Candle attribute is set to your desired candle class. ### \_\_init\_\_ @@ -203,3 +209,34 @@ Rename columns of the data object. | Type | Description | |-----------|---------------------------------------------------------------------------| | `Candles` | A new instance of the class with the renamed columns if inplace is False. | + + + +### plot +```python +def plot(subplots=None, span: int = None, filename="", **kwargs): +``` +Create a plot with mplfinance, can be saved as png. + +#### Parameters: +| Name | Type | Description | Default | +|------------|--------|-------------------------|---------| +| `subplots` | `dict` | Add subplots | None | +| `span` | `int` | Take the last n candles | None | +| `filename` | `str` | A name to save plot | "" | +| `**kwargs` | `Any` | Kwargs to plot function | | + + + +### make_subplot +```python +def make_subplot(column: str | list[str], span: int = None, **kwargs): +``` +Create a plot with mplfinance, can be saved as png. + +#### Parameters: +| Name | Type | Description | Default | +|------------|-------------------|--------------------------------------------------|---------| +| `column` | `list[str]\| str` | An iterable of column names as a list or strings | None | +| `span` | `int` | Take the last n candles | None | +| `**kwargs` | `Any` | Kwargs to plot function | | diff --git a/docs/lib/ticks.md b/docs/lib/ticks.md index 6330195..b5fcb56 100644 --- a/docs/lib/ticks.md +++ b/docs/lib/ticks.md @@ -33,6 +33,7 @@ Price Tick of a Financial Instrument. | `flags` | `TickFlag` | Tick flags | None | | `volume_real` | `float` | Volume for the current Last price | None | | `Index` | `int` | Custom attribute representing the position of the tick in a sequence. | None | +| `index` | `int` | Index of the tick in the input dataframe object. | None | @@ -86,12 +87,13 @@ Supports iteration, slicing and assignment. Similar to `Candles` class but for p | `flags` | `Series` | Tick flags | None | | `volume_real` | `Series` | Volume for the current Last price | None | | `Index` | `Series` | Custom attribute representing the position of the tick in a sequence | None | +| `index` | `Series` | Custom attribute representing the index of the tick in the DataFrame | None | ### \__init\__ ```python -def __init__(*, data: DataFrame | Iterable, flip=False) +def __init__(*, data: DataFrame | Iterable, flip=False): ``` Initialize the Ticks class. Creates a DataFrame of price ticks from the data argument. #### Arguments: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ac3ed1a..0000000 --- a/requirements.txt +++ /dev/null @@ -1,158 +0,0 @@ -anyio==4.3.0 -argon2-cffi==23.1.0 -argon2-cffi-bindings==21.2.0 -arrow==1.3.0 -asttokens==2.4.1 -async-lru==2.0.4 -attrs==23.2.0 -Babel==2.14.0 -beautifulsoup4==4.12.3 -black==23.9.1 -bleach==6.1.0 -build==1.2.2.post1 -certifi==2023.7.22 -cffi==1.16.0 -charset-normalizer==3.3.0 -click==8.1.7 -colorama==0.4.6 -comm==0.2.2 -contourpy==1.2.1 -cycler==0.12.1 -databind.core==4.4.1 -databind.json==4.4.1 -debugpy==1.8.1 -decorator==5.1.1 -defusedxml==0.7.1 -Deprecated==1.2.14 -docspec==2.2.1 -docspec-python==2.2.1 -docstring-parser==0.11 -docutils==0.20.1 -docutils-stubs==0.0.22 -executing==2.0.1 -fastjsonschema==2.19.1 -fonttools==4.51.0 -fqdn==1.5.1 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -idna==3.4 -importlib-metadata==6.8.0 -iniconfig==2.0.0 -ipykernel==6.29.4 -ipython==8.23.0 -ipywidgets==8.1.2 -isoduration==20.11.0 -jaraco.classes==3.3.0 -jedi==0.19.1 -Jinja2==3.1.2 -json5==0.9.24 -jsonpointer==2.4 -jsonschema==4.21.1 -jsonschema-specifications==2023.12.1 -jupyter==1.0.0 -jupyter-console==6.6.3 -jupyter-events==0.10.0 -jupyter-lsp==2.2.5 -jupyter_client==8.6.1 -jupyter_core==5.7.2 -jupyter_server==2.13.0 -jupyter_server_terminals==0.5.3 -jupyterlab==4.1.6 -jupyterlab_pygments==0.3.0 -jupyterlab_server==2.26.0 -jupyterlab_widgets==3.0.10 -keyring==24.2.0 -kiwisolver==1.4.5 -markdown-it-py==3.0.0 -MarkupSafe==2.1.3 -matplotlib==3.8.4 -matplotlib-inline==0.1.6 -mdurl==0.1.2 -MetaTrader5==5.0.4424 -mistune==3.0.2 -more-itertools==10.1.0 -mplfinance==0.12.10b0 -mypy-extensions==1.0.0 -nbclient==0.10.0 -nbconvert==7.16.3 -nbformat==5.10.4 -nest-asyncio==1.6.0 -nh3==0.2.14 -notebook==7.1.2 -notebook_shim==0.2.4 -nr-date==2.1.0 -nr-stream==1.1.5 -nr.util==0.8.12 -numpy==1.26.0 -overrides==7.7.0 -packaging==23.2 -pandas==2.1.1 -pandas-ta==0.3.14b0 -pandocfilters==1.5.1 -parso==0.8.4 -pathspec==0.11.2 -pillow==10.3.0 -pkginfo==1.9.6 -platformdirs==3.11.0 -pluggy==1.5.0 -prometheus_client==0.20.0 -prompt-toolkit==3.0.43 -psutil==5.9.8 -pure-eval==0.2.2 -pycparser==2.22 -pydoc-markdown==4.8.2 -Pygments==2.16.1 -pyparsing==3.1.2 -pyproject_hooks==1.0.0 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-order==1.3.0 -python-dateutil==2.8.2 -python-json-logger==2.0.7 -python-telegram-bot==21.0.1 -pytz==2023.3.post1 -pywin32==306 -pywin32-ctypes==0.2.2 -pywinpty==2.0.13 -PyYAML==6.0.1 -pyzmq==25.1.2 -qtconsole==5.5.1 -QtPy==2.4.1 -readme-renderer==42.0 -referencing==0.34.0 -requests==2.31.0 -requests-toolbelt==1.0.0 -rfc3339-validator==0.1.4 -rfc3986==2.0.0 -rfc3986-validator==0.1.1 -rich==13.6.0 -rpds-py==0.18.0 -ruff==0.7.3 -Send2Trash==1.8.3 -six==1.16.0 -sniffio==1.3.1 -soupsieve==2.5 -stack-data==0.6.3 -terminado==0.18.1 -tinycss2==1.2.1 -tomli==2.0.1 -tomli_w==1.0.0 -tornado==6.4 -traitlets==5.14.2 -twine==5.1.1 -typeapi==2.1.1 -types-python-dateutil==2.9.0.20240316 -typing_extensions==4.6.3 -tzdata==2023.3 -uri-template==1.3.0 -urllib3==2.0.6 -watchdog==3.0.0 -wcwidth==0.2.13 -webcolors==1.13 -webencodings==0.5.1 -websocket-client==1.7.0 -widgetsnbextension==4.0.10 -wrapt==1.15.0 -yapf==0.40.2 -zipp==3.17.0 diff --git a/src/aiomql/core/config.py b/src/aiomql/core/config.py index 1e42dab..e61d523 100644 --- a/src/aiomql/core/config.py +++ b/src/aiomql/core/config.py @@ -25,8 +25,10 @@ class Config: root: Path record_trades: bool records_dir: Path + plots_dir: Path backtest_dir: Path records_dir_name: str + plots_dir_name: str backtest_dir_name: str #Todo: add to docs task_queue: TaskQueue _backtest_engine: BackTestEngine @@ -54,6 +56,7 @@ class Config: "shutdown": False, "force_shutdown": False, "root": None, + "plots_dir_name": "plots" } def __new__(cls, *args, **kwargs): @@ -110,7 +113,7 @@ class Config: if config_file.exists(): return config_file if current == self.root: - return + return None for dirname in current.parents: config_file = dirname / self.filename if config_file.exists(): @@ -118,9 +121,10 @@ class Config: if self.root == dirname: break + return None except Exception as err: logger.debug(f"Error finding config file: {err}") - + return None def load_config(self, *, config_file: str | Path = None, filename: str = None, root: str | Path = None, **kwargs) -> Self: """Load configuration settings from a file and reset the config object. @@ -182,6 +186,12 @@ class Config: b_dir.mkdir(parents=True, exist_ok=True) if b_dir.exists() is False else ... return b_dir + @property + def plots_dir(self): + p_dir = self.root / self.plots_dir_name or "plots" + p_dir.mkdir(parents=True, exist_ok=True) if p_dir.exists() is False else ... + return p_dir + def account_info(self) -> dict[str, int | str]: """Returns Account login details as found in the config object if available diff --git a/src/aiomql/lib/candle.py b/src/aiomql/lib/candle.py index 7cbdeb1..ed476ea 100644 --- a/src/aiomql/lib/candle.py +++ b/src/aiomql/lib/candle.py @@ -5,10 +5,12 @@ from typing import Type, Self, Iterable from logging import getLogger import pandas as pd +import mplfinance as mpf import pandas_ta as ta from pandas import DataFrame, Series, DatetimeIndex, Timestamp from ..core.constants import TimeFrame +from ..core._core import Config logger = getLogger(__name__) @@ -203,6 +205,7 @@ class Candles: self._data.index = pd.DatetimeIndex(self._data.time, dtype=dtype) self.Candle = candle_class or Candle + self.config = Config() def __repr__(self): return repr(self._data) @@ -346,3 +349,39 @@ class Candles: return self else: raise TypeError("Expected Series, DataFrame or Candle, got {}".format(type(obj))) + + def plot(self, subplots: dict = None, span: int = None, filename="", **kwargs): + """ + Create a plot of the candles + + Args: + subplots (dict): Subplots to bed added to the main plot + span (int): Last 'n' candles to be used for making the subplot + filename (str): A filename to saved the plot + **kwargs: Kwargs to be passed to the plot + """ + type_ = kwargs.pop("type", "candle") + subplots = subplots or [] + span = 0 if span is None else span + data = self._data[-span:] + if filename and not kwargs.get("savefig"): + kwargs["savefig"] = self.config.plots_dir / filename + + mpf.plot(data, type=type_, addplot=subplots, **kwargs) + + def make_subplot(self, *, column: str | list[str], span: int = None, **kwargs) -> dict: + """ + Create a subplot + Args: + column (list[str] | str): Name of columns for the subplot + span (int): Last 'n' candles to be used for making the subplot + **kwargs: Keywords arguments to pass to the subplot + + Returns: + dict: Subplots + """ + column = column if isinstance(column, list) else [column] + span = 0 if span is None else span + data = self._data[-span:] + data = data[column] + return mpf.make_addplot(data, **kwargs) diff --git a/src/aiomql/lib/ticks.py b/src/aiomql/lib/ticks.py index 510ab2d..4750d89 100644 --- a/src/aiomql/lib/ticks.py +++ b/src/aiomql/lib/ticks.py @@ -23,6 +23,7 @@ class Tick: flags (TickFlag): Tick flags volume_real (float): Volume for the current Last price Index (int): Custom attribute representing the position of the tick in a sequence. + index (int): Index of the tick in the input dataframe object. """ time: float bid: float From 06f33c42a6d9d8017ad9c333376d66718cea7ce5 Mon Sep 17 00:00:00 2001 From: Victor Joseph Date: Sun, 29 Jun 2025 12:17:28 +0100 Subject: [PATCH 2/2] update requirements --- requirements.txt | 118 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fa86efa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,118 @@ +-e git+ssh://git@github.com/VChijioke/aiomql.git@626658d68ac771353b424d50a33e7dd0de6a9054#egg=aiomql +anyio==4.9.0 +argon2-cffi==25.1.0 +argon2-cffi-bindings==21.2.0 +arrow==1.3.0 +asttokens==3.0.0 +async-lru==2.0.5 +attrs==25.3.0 +babel==2.17.0 +beautifulsoup4==4.13.4 +bleach==6.2.0 +certifi==2025.4.26 +cffi==1.17.1 +charset-normalizer==3.4.2 +colorama==0.4.6 +comm==0.2.2 +contourpy==1.3.2 +cycler==0.12.1 +debugpy==1.8.14 +decorator==5.2.1 +defusedxml==0.7.1 +executing==2.2.0 +fastjsonschema==2.21.1 +fonttools==4.58.2 +fqdn==1.5.1 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +idna==3.10 +iniconfig==2.1.0 +ipykernel==6.29.5 +ipython==9.3.0 +ipython_pygments_lexers==1.1.1 +ipywidgets==8.1.7 +isoduration==20.11.0 +jedi==0.19.2 +Jinja2==3.1.6 +json5==0.12.0 +jsonpointer==3.0.0 +jsonschema==4.24.0 +jsonschema-specifications==2025.4.1 +jupyter==1.1.1 +jupyter-console==6.6.3 +jupyter-events==0.12.0 +jupyter-lsp==2.2.5 +jupyter_client==8.6.3 +jupyter_core==5.8.1 +jupyter_server==2.16.0 +jupyter_server_terminals==0.5.3 +jupyterlab==4.4.3 +jupyterlab_pygments==0.3.0 +jupyterlab_server==2.27.3 +jupyterlab_widgets==3.0.15 +kiwisolver==1.4.8 +MarkupSafe==3.0.2 +matplotlib==3.10.3 +matplotlib-inline==0.1.7 +MetaTrader5==5.0.5050 +mistune==3.1.3 +mplfinance==0.12.10b0 +nbclient==0.10.2 +nbconvert==7.16.6 +nbformat==5.10.4 +nest-asyncio==1.6.0 +notebook==7.4.3 +notebook_shim==0.2.4 +numpy==1.26.0 +overrides==7.7.0 +packaging==25.0 +pandas==2.3.0 +pandas_ta==0.3.14b0 +pandocfilters==1.5.1 +parso==0.8.4 +pillow==11.2.1 +platformdirs==4.3.8 +pluggy==1.6.0 +prometheus_client==0.22.1 +prompt_toolkit==3.0.51 +psutil==7.0.0 +pure_eval==0.2.3 +pycparser==2.22 +Pygments==2.19.1 +pyparsing==3.2.3 +pytest==8.4.1 +pytest-asyncio==1.0.0 +pytest-order==1.3.0 +python-dateutil==2.9.0.post0 +python-json-logger==3.3.0 +pytz==2025.2 +pywin32==310 +pywinpty==2.0.15 +PyYAML==6.0.2 +pyzmq==26.4.0 +referencing==0.36.2 +requests==2.32.3 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rpds-py==0.25.1 +Send2Trash==1.8.3 +setuptools==80.9.0 +six==1.17.0 +sniffio==1.3.1 +soupsieve==2.7 +stack-data==0.6.3 +terminado==0.18.1 +tinycss2==1.4.0 +tornado==6.5.1 +traitlets==5.14.3 +types-python-dateutil==2.9.0.20250516 +typing_extensions==4.14.0 +tzdata==2025.2 +uri-template==1.3.0 +urllib3==2.4.0 +wcwidth==0.2.13 +webcolors==24.11.1 +webencodings==0.5.1 +websocket-client==1.8.0 +widgetsnbextension==4.0.14