mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-06 08:47:47 +00:00
v4
This commit is contained in:
+6
-77
@@ -2,66 +2,21 @@
|
||||
|
||||
## Table of Contents
|
||||
- [Account](#account.Account)
|
||||
- [\_\_init\_\_](#account.__init__)
|
||||
- [\_\_aenter\_\_](#account.__aenter__)
|
||||
- [\_\_aexit\_\_](#account.__aexit__)
|
||||
- [sign_in](#account.sign_in)
|
||||
- [refresh](#account.refresh)
|
||||
- [has_symbol](#account.has_symbol)
|
||||
- [symbols_get](#account.symbols_get)
|
||||
|
||||
<a id="Account"></a>
|
||||
|
||||
<a id="account.Account"></a>
|
||||
### Account
|
||||
```python
|
||||
class Account(AccountInfo)
|
||||
class Account(_Base, AccountInfo)
|
||||
```
|
||||
Singleton class for managing a trading account. A subclass of AccountInfo.
|
||||
All AccountInfo attributes are available in this class.
|
||||
A singleton class for managing a trading account. A subclass of _Base and AccountInfo. It supports asynchronous context
|
||||
management protocol.
|
||||
|
||||
#### Attributes
|
||||
| Name | Type | Description | Default |
|
||||
|-------------|-------------------|------------------------------------------------------|---------|
|
||||
| `connected` | `bool` | Status of connection to MetaTrader 5 Terminal | False |
|
||||
| `symbols` | `set[SymbolInfo]` | A set of available symbols for the financial market. | set() |
|
||||
|
||||
<a id="account.__init__"></a>
|
||||
#### \_\_init\_\_
|
||||
```python
|
||||
def __init__(self, *args, **kwargs)
|
||||
```
|
||||
Initializes the Account class. Inherits all attributes from the AccountInfo class.
|
||||
|
||||
<a id="account.__aenter__"></a>
|
||||
### __aenter__
|
||||
```python
|
||||
async def __aenter__() -> 'Account'
|
||||
```
|
||||
Async context manager for the Account class. Connects to a trading account and returns the account instance.
|
||||
#### Returns:
|
||||
| Type | Description |
|
||||
|-----------|----------------------------------|
|
||||
| `Account` | An instance of the Account class |
|
||||
#### Raises:
|
||||
| Exception | Description |
|
||||
|--------------|----------------|
|
||||
| `LoginError` | If login fails |
|
||||
|
||||
<a id="account.__aexit__"></a>
|
||||
### __aexit__
|
||||
```python
|
||||
async def __aexit__(exc_type, exc_value, traceback)
|
||||
```
|
||||
Async context manager for the Account class. Disconnects from the trading account.
|
||||
|
||||
<a id="account.sign_in"></a>
|
||||
### sign_in
|
||||
```python
|
||||
async def sign_in() -> bool
|
||||
```
|
||||
Connect to a trading account.
|
||||
#### Returns:
|
||||
| Type | Description |
|
||||
|--------|-----------------------------------------|
|
||||
| `bool` | True if login was successful else False |
|
||||
|
||||
<a id="account.refresh"></a>
|
||||
### refresh
|
||||
@@ -69,29 +24,3 @@ Connect to a trading account.
|
||||
async def refresh()
|
||||
```
|
||||
Refreshes the account instance with the latest data from the MetaTrader 5 terminal
|
||||
|
||||
<a id="account.has_symbol"></a>
|
||||
### has_symbol
|
||||
```python
|
||||
def has_symbol(symbol: str | Type[SymbolInfo])
|
||||
```
|
||||
Checks to see if a symbol is available for a trading account
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|----------|---------------------|--------------------------------------|
|
||||
| `symbol` | `str`\|`SymbolInfo` | A symbol name or SymbolInfo instance |
|
||||
#### Returns:
|
||||
| Type | Description |
|
||||
|--------|----------------------------------------|
|
||||
| `bool` | True if symbol is available else False |
|
||||
|
||||
<a id="account.symbols_get"></a>
|
||||
### symbols_get
|
||||
```python
|
||||
async def symbols_get() -> set[SymbolInfo]
|
||||
```
|
||||
Get all financial instruments from the MetaTrader 5 terminal available for the current account.
|
||||
#### Returns:
|
||||
| Type | Description |
|
||||
|-------------------|-------------------------------|
|
||||
| `set[SymbolInfo]` | A set of SymbolInfo instances |
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
# Bot
|
||||
|
||||
## Table of Contents
|
||||
- [Bot](#bot.Bot)
|
||||
- [\_\_init\_\_](#bot.init)
|
||||
- [initialize](#bot.initialize)
|
||||
- [execute](#bot.execute)
|
||||
- [start](#bot.start)
|
||||
- [add_coroutine](#bot.add_coroutine)
|
||||
- [add_function](#bot.add_function)
|
||||
- [add_strategy](#bot.add_strategy)
|
||||
- [add_strategies](#bot.add_strategies)
|
||||
- [add_strategy_all](#bot.add_strategy_all)
|
||||
- [process_pool](#bot.run_bots)
|
||||
|
||||
<a id='bot.Bot'></a>
|
||||
### Bot
|
||||
```python
|
||||
class Bot
|
||||
```
|
||||
"""The bot class. Create a bot instance to run strategies.
|
||||
|
||||
#### Attributes.
|
||||
| Name | Type | Description | Default |
|
||||
|--------------|--------------------|--------------------------------------------|--------------|
|
||||
| `account` | `Account` | Account Object. | None |
|
||||
| `executor` | `Executor` | The executor. | None |
|
||||
| `strategies` | `List[Strategies]` | A list of strategies to initialize and run | list() |
|
||||
| `mt5` | `MetaTrader` | `A MetaTrader Instance` | MetaTrader() |
|
||||
| `config` | `Config` | A Config instance | Config() |
|
||||
|
||||
<a id='bot.init'></a>
|
||||
### \_\_init\_\_
|
||||
```python
|
||||
def __init__()
|
||||
```
|
||||
Initializes the Bot class.
|
||||
|
||||
<a id='bot.initialize'></a>
|
||||
### initialize
|
||||
```python
|
||||
async def initialize(self)
|
||||
```
|
||||
Prepares the bot by signing in to the trading account and initializing the symbols for each strategy.
|
||||
Only strategies with successfully initialized symbols will be added to the executor. Starts the global task queue.
|
||||
|
||||
Note: *initialize_sync* is a synchronous version of this method.
|
||||
|
||||
#### Raises:
|
||||
| Exception | Description |
|
||||
|--------------|-------------------------------|
|
||||
| `SystemExit` | If sign in was not successful |
|
||||
|
||||
|
||||
|
||||
<a id='bot.execute'></a>
|
||||
### execute
|
||||
```python
|
||||
def execute()
|
||||
```
|
||||
Executes the bot. Use this method to run the bot in a synchronous manner.
|
||||
This method is blocking and will not return until the bot is done running.
|
||||
|
||||
<a id='bot.start'></a>
|
||||
### start
|
||||
```python
|
||||
async def start()
|
||||
```
|
||||
Initialize the bot and execute it. Similar to calling **execute** method but is asynchronous.
|
||||
|
||||
<a id='bot.add_coroutine'></a>
|
||||
### add_coroutine
|
||||
```python
|
||||
def add_coroutine(self, coroutine: Coroutine, on_separate_thread=False, **kwargs)
|
||||
```
|
||||
Add a coroutine to the executor. By default, all coroutines added to the executor run on this same thread,
|
||||
using _asyncio.gather_, but if _on_separate_thread_ is true then the coroutine is given it's own thread.
|
||||
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|----------------------|-------------|----------------------------------------------------|
|
||||
| `coroutine` | `Coroutine` | A coroutine to run in the executor |
|
||||
| `on_separate_thread` | `bool` | Run coroutine on a separate thread in the executor |
|
||||
| `kwargs` | `Any` | Keyword arguments to pass to the coroutine |
|
||||
|
||||
<a id='bot.add_function'></a>
|
||||
### add_function
|
||||
```python
|
||||
def add_function(self, function: Callable, **kwargs)
|
||||
```
|
||||
Add a function to the executor.
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|------------|------------|-------------------------------------------|
|
||||
| `function` | `Callable` | A function to run in the executor |
|
||||
| `kwargs` | `Any` | Keyword arguments to pass to the function |
|
||||
|
||||
<a id='bot.add_strategy'></a>
|
||||
### add_strategy
|
||||
```python
|
||||
def add_strategy(self, strategy: Strategy)
|
||||
```
|
||||
Add a strategy to the list of strategies.
|
||||
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|------------|------------|-----------------------------------|
|
||||
| `strategy` | `Strategy` | A Strategy instance to run on bot |
|
||||
|
||||
<a id='bot.add_strategies'></a>
|
||||
### add_strategies
|
||||
```python
|
||||
def add_strategies(strategies: Iterable[Strategy])
|
||||
```
|
||||
Add multiple strategies at the same time
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|--------------|----------------------|-----------------------------------|
|
||||
| `strategies` | `Iterable[Strategy]` | An iterable of Strategy instances |
|
||||
|
||||
<a id='bot.add_strategy_all'></a>
|
||||
### add_strategy_all
|
||||
```python
|
||||
def add_strategy_all(*, strategy: Type[Strategy], params: dict | None = None, symbols: list[Symbol] = None, **kwargs)
|
||||
```
|
||||
Use this to run a single strategy on multiple symbols with the same parameters and keyword arguments.
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|------------|------------------|---------------------------------------------|
|
||||
| `strategy` | `Type[Strategy]` | A Strategy class |
|
||||
| `params` | `dict` or `None` | A dictionary of parameters for the strategy |
|
||||
| `symbols` | `list[Symbol]` | A list of symbols to run the strategy on |
|
||||
| `**kwargs` | `Any` | Keyword arguments |
|
||||
|
||||
|
||||
<a id='bot.process_pool'></a>
|
||||
```python
|
||||
@classmethod
|
||||
def process_pool(cls, bots: dict[Callable: dict] = None, num_workers: int = None):
|
||||
```
|
||||
Run multiple functions (scripts, bots) at the same time in parallel with different accounts.
|
||||
Running multiple functions is useful when you want to run different strategies on different accounts.
|
||||
The callable can for example be a bot instance that defines its own Config instance within the function scope.
|
||||
The dictionary should contain the callable as the key and the dictionary of keyword arguments to pass to the callable as
|
||||
the value. Use the path attribute of the config instance to specify the terminal path of each account.
|
||||
The num_workers parameter specifies the number of workers to use. If not specified, the number of workers will be the
|
||||
number of bots.
|
||||
|
||||
#### Parameters
|
||||
| Name | Type | Description |
|
||||
|--------------|------------------------|---------------------------------------------------------------------------------|
|
||||
| `bots` | `dict[Callable: dict]` | A dictionary of callables and their keyword arguments to run as bots |
|
||||
| `num_workers` | `int` | The number of workers to use. If not specified, the number of bots will be used |
|
||||
@@ -1,162 +0,0 @@
|
||||
# Bot
|
||||
|
||||
## Table of Contents
|
||||
- [Bot](#bb.Bot)
|
||||
- [\_\_init\_\_](#bb.__init__)
|
||||
- [initialize](#bb.initialize)
|
||||
- [execute](#bb.execute)
|
||||
- [start](#bb.start)
|
||||
- [add_coroutine](#bb.add_coroutine)
|
||||
- [add_function](#bb.add_function)
|
||||
- [add_strategy](#bb.add_strategy)
|
||||
- [add_strategies](#bb.add_strategies)
|
||||
- [add_strategy_all](#bb.add_strategy_all)
|
||||
- [init_symbols](#bb.init_symbols)
|
||||
- [init_symbol](#bb.init_symbol]())
|
||||
- [run_bots](#bb.run_bots)
|
||||
|
||||
<a id='bb.Bot'></a>
|
||||
### Bot
|
||||
```python
|
||||
class Bot
|
||||
```
|
||||
The bot class. Create a bot instance to run your strategies.
|
||||
#### Attributes:
|
||||
| Name | Type | Description | Default |
|
||||
|------------|----------------------|------------------------------------------|----------|
|
||||
| `account` | `Account` | Account Object. | None |
|
||||
| `executor` | `ThreadPoolExecutor` | The default thread executor. | None |
|
||||
| `symbols` | `set[Symbols]` | A set of symbols for the trading session | set() |
|
||||
| `config` | `Config` | A Config instance | Config() |
|
||||
|
||||
<a id='bb.__init__'></a>
|
||||
### \_\_init\_\_
|
||||
```python
|
||||
def __init__()
|
||||
```
|
||||
Initializes the Bot class.
|
||||
|
||||
<a id='bb.initialize'></a>
|
||||
### initialize
|
||||
```python
|
||||
async def initialize()
|
||||
```
|
||||
Prepares the bot by signing in to the trading account and initializing the symbols for the trading session.
|
||||
#### Raises:
|
||||
| Exception | Description |
|
||||
|--------------|-------------------------------|
|
||||
| `SystemExit` | If sign in was not successful |
|
||||
|
||||
<a id='bb.execute'></a>
|
||||
### execute
|
||||
```python
|
||||
def execute()
|
||||
```
|
||||
Execute the bot. Use this method to run the bot.
|
||||
|
||||
<a id='bb.start'></a>
|
||||
### start
|
||||
```python
|
||||
async def start()
|
||||
```
|
||||
Initialize the bot and execute it. Similar to calling **execute** method but is asynchronous.
|
||||
|
||||
<a id='bb.add_coroutine'></a>
|
||||
### add_coroutine
|
||||
```python
|
||||
def add_coroutine(coro: Coroutine, **kwargs)
|
||||
```
|
||||
Add a coroutine to the executor.
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|----------|-------------|--------------------------------------------|
|
||||
| `coro` | `Coroutine` | A coroutine to run in the executor |
|
||||
| `kwargs` | `Any` | Keyword arguments to pass to the coroutine |
|
||||
|
||||
<a id='bb.add_function'></a>
|
||||
### add_function
|
||||
```python
|
||||
def add_function(func: Callable, **kwargs)
|
||||
```
|
||||
Add a function to the executor.
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|----------|------------|-------------------------------------------|
|
||||
| `func` | `Callable` | A function to run in the executor |
|
||||
| `kwargs` | `Any` | Keyword arguments to pass to the function |
|
||||
|
||||
<a id='bb.add_strategy'></a>
|
||||
### add_strategy
|
||||
```python
|
||||
def add_strategy(strategy: Strategy)
|
||||
```
|
||||
Add a strategy to the executor. An added strategy will only run if it's symbol was successfully initialized.
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|------------|------------|-----------------------------------|
|
||||
| `strategy` | `Strategy` | A Strategy instance to run on bot |
|
||||
|
||||
<a id='bb.add_strategies'></a>
|
||||
### add_strategies
|
||||
```python
|
||||
def add_strategies(strategies: Iterable[Strategy])
|
||||
```
|
||||
Add multiple strategies at the same time
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|--------------|----------------------|-----------------------------------|
|
||||
| `strategies` | `Iterable[Strategy]` | An iterable of Strategy instances |
|
||||
|
||||
<a id='bb.add_strategy_all'></a>
|
||||
### add_strategy_all
|
||||
```python
|
||||
def add_strategy_all(*, strategy: Type[Strategy], params: dict | None = None)
|
||||
```
|
||||
Use this to run a single strategy on all available instruments in the market using the default parameters
|
||||
i.e. one set of parameters for all trading symbols
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|------------|------------------|---------------------------------------------|
|
||||
| `strategy` | `Type[Strategy]` | A Strategy class |
|
||||
| `params` | `dict` or `None` | A dictionary of parameters for the strategy |
|
||||
|
||||
<a id='bb.init_symbols'></a>
|
||||
### init_symbols
|
||||
```python
|
||||
async def init_symbols()
|
||||
```
|
||||
Initialize the symbols for the current trading session. This method is called internally by the bot.
|
||||
|
||||
<a id='bb.init_symbol'></a>
|
||||
### init_symbol
|
||||
```python
|
||||
async def init_symbol(symbol: Symbol) -> Symbol
|
||||
```
|
||||
Initialize a symbol before the beginning of a trading session.
|
||||
Removes it from the list of symbols if it was not successfully initialized or not available for the account.
|
||||
#### Parameters:
|
||||
| Name | Type | Description |
|
||||
|----------|----------|-------------------|
|
||||
| `symbol` | `Symbol` | A Symbol instance |
|
||||
#### Returns:
|
||||
| Type | Description |
|
||||
|----------|-------------------|
|
||||
| `Symbol` | A Symbol instance |
|
||||
|
||||
<a id='bb.run_bots'></a>
|
||||
```python
|
||||
@classmethod
|
||||
def run_bots(cls, funcs: dict[Callable: dict] = None, num_workers: int = None):
|
||||
```
|
||||
Run multiple functions (scripts, bots) at the same time in parallel with different accounts.
|
||||
Running multiple functions is useful when you want to run different strategies on different accounts.
|
||||
The callable can for example be a bot instance that defines its own Config instance within the function scope.
|
||||
The dictionary should contain the callable as the key and the dictionary of keyword arguments to pass to the callable as
|
||||
the value. Use the path attribute of the config instance to specify the terminal path of each account.
|
||||
The num_workers parameter specifies the number of workers to use. If not specified, the number of workers will be the
|
||||
number of bots.
|
||||
#### Parameters
|
||||
| Name | Type | Description |
|
||||
|---------------|------------------------|---------------------------------------------------------------------------------|
|
||||
| `funcs` | `dict[Callable: dict]` | A dictionary of callables and their keyword arguments to run as bots |
|
||||
| `num_workers` | `int` | The number of workers to use. If not specified, the number of bots will be used |
|
||||
@@ -3,7 +3,6 @@ import asyncio
|
||||
import json
|
||||
from datetime import datetime, UTC
|
||||
from typing import Literal
|
||||
from itertools import zip_longest
|
||||
import random
|
||||
from functools import cached_property
|
||||
from logging import getLogger
|
||||
@@ -60,7 +59,7 @@ class BackTestEngine:
|
||||
range: range
|
||||
speed: int
|
||||
cursor: Cursor
|
||||
iter: zip_longest
|
||||
iter: zip
|
||||
rates: dict[str, dict[int, DataFrame]]
|
||||
ticks: dict[str, DataFrame]
|
||||
prices: dict[str, DataFrame]
|
||||
@@ -76,7 +75,6 @@ class BackTestEngine:
|
||||
preloaded_ticks: dict[str, DataFrame]
|
||||
preload: bool
|
||||
account_lock: RLock
|
||||
use_termial_in_tracker: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -89,10 +87,9 @@ class BackTestEngine:
|
||||
use_terminal: bool = None,
|
||||
name: str = "",
|
||||
stop_time: float | datetime = None,
|
||||
close_open_positions_on_exit: bool = False,
|
||||
close_open_positions_on_exit: bool = True,
|
||||
preload=True,
|
||||
assign_to_config: bool = False,
|
||||
use_termial_in_tracker: bool = False,
|
||||
):
|
||||
self._data = data or BackTestData()
|
||||
self.mt5 = MetaTrader()
|
||||
@@ -171,11 +168,14 @@ class BackTestEngine:
|
||||
self.speed = speed
|
||||
self.span = range(span_start, span_end, speed)
|
||||
self.range = range(0, span_end - span_start, speed)
|
||||
self.iter = zip_longest(self.range, self.span)
|
||||
self.iter = zip(self.range, self.span)
|
||||
|
||||
if restart is False and self._data.cursor is not None:
|
||||
self.cursor = self._data.cursor
|
||||
self.go_to(time=self.cursor.time)
|
||||
new_range = range(self.range[self.cursor.index], self.range.stop)
|
||||
new_span = range(self.span[self.cursor.index], self.span.stop)
|
||||
self.iter = zip(new_range, new_span)
|
||||
# self.go_to(time=self.cursor.time)
|
||||
else:
|
||||
self.cursor = Cursor(index=self.range.start, time=self.span.start)
|
||||
|
||||
@@ -220,7 +220,7 @@ class BackTestEngine:
|
||||
return self._data
|
||||
|
||||
def reset(self, clear_data: bool = False):
|
||||
self.iter = zip_longest(self.range, self.span)
|
||||
self.iter = zip(self.range, self.span)
|
||||
self.cursor = Cursor(index=self.range.start, time=self.span.start)
|
||||
if clear_data:
|
||||
self.setup_data(restart=True)
|
||||
@@ -234,6 +234,7 @@ class BackTestEngine:
|
||||
time = int(time.timestamp())
|
||||
steps = time - self.cursor.time
|
||||
steps = steps // self.speed
|
||||
steps = max(steps, 1)
|
||||
if 0 <= steps < (len(self.range) - 1):
|
||||
self.fast_forward(steps=steps)
|
||||
return
|
||||
|
||||
@@ -17,6 +17,7 @@ from MetaTrader5 import (
|
||||
OrderSendResult,
|
||||
OrderCheckResult,
|
||||
)
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
from .constants import OrderType, CopyTicks
|
||||
|
||||
@@ -102,6 +103,33 @@ class MetaTrader(MetaCore):
|
||||
self._login, login, password=password, server=server, timeout=timeout
|
||||
)
|
||||
|
||||
def login_sync(
|
||||
self,
|
||||
*,
|
||||
login: int = 0,
|
||||
password: str = "",
|
||||
server: str = "",
|
||||
timeout: int = 60000,
|
||||
) -> bool:
|
||||
"""
|
||||
Connects to the MetaTrader terminal using the specified login, password and server.
|
||||
|
||||
Args:
|
||||
login (int): The trading account number.
|
||||
password (str): The trading account password.
|
||||
server (str): The trading server name.
|
||||
timeout (int): The timeout for the connection in seconds.
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise.
|
||||
"""
|
||||
acc_details = self.config.account_info()
|
||||
login = login or acc_details.get("login", 0)
|
||||
password = password or acc_details.get("password", "")
|
||||
server = server or acc_details.get("server", "")
|
||||
res = mt5.login(login, password=password, server=server, timeout=timeout)
|
||||
return res
|
||||
|
||||
async def initialize(
|
||||
self,
|
||||
path: str = None,
|
||||
@@ -150,6 +178,52 @@ class MetaTrader(MetaCore):
|
||||
self.error = Error(*err)
|
||||
return res
|
||||
|
||||
def initialize_sync(
|
||||
self,
|
||||
path: str = None,
|
||||
login: int = 0,
|
||||
password: str = "",
|
||||
server: str = "",
|
||||
timeout: int | None = None,
|
||||
portable=False,
|
||||
) -> bool:
|
||||
"""
|
||||
Initializes the connection to the MetaTrader terminal. All parameters are optional.
|
||||
|
||||
Keyword Args:
|
||||
path (str): The path to the MetaTrader terminal executable.
|
||||
login (int): The trading account number.
|
||||
password (str): The trading account password.
|
||||
server (str): The trading server name.
|
||||
timeout (int): The timeout for the connection in milliseconds.
|
||||
portable (bool): If True, the terminal will be launched in portable mode.
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise.
|
||||
"""
|
||||
path = self.config.path if path is None else path
|
||||
path = "" if Path(path).exists() is False else path
|
||||
args = (str(path),) if path else ()
|
||||
acc = self.config.account_info()
|
||||
kwargs = {
|
||||
key: value
|
||||
for key, value in (
|
||||
("login", login or acc.get("login")),
|
||||
("password", password or acc.get("password")),
|
||||
("server", server or acc.get("server")),
|
||||
("timeout", timeout or 60000),
|
||||
("portable", portable),
|
||||
)
|
||||
if key is not None
|
||||
}
|
||||
res = mt5.initialize(*args, **kwargs)
|
||||
if res is False:
|
||||
self._shutdown()
|
||||
if not res:
|
||||
err = self._last_error()
|
||||
self.error = Error(*err)
|
||||
return res
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""Closes the connection to the MetaTrader terminal."""
|
||||
self._shutdown()
|
||||
|
||||
@@ -9,16 +9,12 @@ logger = getLogger(__name__)
|
||||
|
||||
|
||||
class Account(_Base, AccountInfo):
|
||||
"""A class for managing a trading account. A singleton class.
|
||||
A subclass of AccountInfo. All AccountInfo attributes are available in this class.
|
||||
"""A singleton class for managing a trading account. A subclass of _Base and AccountInfo. It supports
|
||||
Asynchronous context management protocol.
|
||||
|
||||
Attributes:
|
||||
connected (bool): Status of connection to MetaTrader 5 Terminal
|
||||
|
||||
Notes:
|
||||
Other Account properties are defined in the AccountInfo class.
|
||||
"""
|
||||
|
||||
_instance: Self
|
||||
connected: bool
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class BackTester:
|
||||
async def start(self):
|
||||
"""Initialize the bot and execute it. Similar to calling `execute` method but is a coroutine."""
|
||||
await self.initialize()
|
||||
await self.executor.execute()
|
||||
self.executor.execute()
|
||||
|
||||
def add_strategy(self, *, strategy: Strategy):
|
||||
"""Add a strategy to the list of strategies.
|
||||
|
||||
+73
-22
@@ -7,21 +7,20 @@ from .executor import Executor
|
||||
from ..core.config import Config
|
||||
from ..core.meta_trader import MetaTrader
|
||||
from .symbol import Symbol as Symbol
|
||||
from .ticks import Tick
|
||||
from .strategy import Strategy as Strategy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Bot:
|
||||
"""The bot class. Create a bot instance to run your strategies.
|
||||
"""The bot class. Creates a bot instance to run strategies.
|
||||
|
||||
Attributes:
|
||||
executor: The default thread executor.
|
||||
executor: A thread executor.
|
||||
config (Config): Config instance
|
||||
mt (MetaTrader): MetaTrader instance
|
||||
|
||||
"""
|
||||
|
||||
config: Config
|
||||
executor: Executor
|
||||
mt: MetaTrader
|
||||
@@ -30,32 +29,33 @@ class Bot:
|
||||
def __init__(self):
|
||||
self.config = Config(bot=self)
|
||||
self.executor = Executor()
|
||||
self.mt = MetaTrader()
|
||||
self.mt5 = MetaTrader()
|
||||
self.strategies = []
|
||||
|
||||
@classmethod
|
||||
def run_bots(cls, funcs: dict[Callable:dict] = None, num_workers: int = None):
|
||||
"""Run multiple scripts or bots in parallel with different accounts.
|
||||
def process_pool(cls, bots: dict[Callable:dict] = None, num_workers: int = None):
|
||||
"""Run multiple bots in parallel using a ProcessPoolExecutor. Each bot should be a callable that accepts
|
||||
keyword arguments only.
|
||||
|
||||
Args:
|
||||
funcs (dict): A dictionary of functions to run with their respective keyword arguments as a dictionary
|
||||
num_workers (int): Number of workers to run the functions
|
||||
bots (dict): A dictionary of bots to run with their respective keyword arguments as a dictionary
|
||||
num_workers (int): Number of workers to run the bots
|
||||
"""
|
||||
num_workers = num_workers or len(funcs)
|
||||
num_workers = num_workers or len(bots) + 1
|
||||
with ProcessPoolExecutor(max_workers=num_workers) as executor:
|
||||
for bot, kwargs in funcs.items():
|
||||
for bot, kwargs in bots.items():
|
||||
executor.submit(bot, **kwargs)
|
||||
|
||||
async def initialize(self):
|
||||
"""Prepares the bot by signing in to the trading account and initializing the symbols for the trading session.
|
||||
Starts the global task queue.
|
||||
"""Prepares the bot by signing in to the trading account and initializing the symbols for each strategy.
|
||||
Only strategies with successfully initialized symbols will be added to the executor. Starts the global task queue.
|
||||
|
||||
Raises:
|
||||
SystemExit if sign in was not successful
|
||||
"""
|
||||
try:
|
||||
await self.mt.initialize()
|
||||
login = await self.mt.login()
|
||||
await self.mt5.initialize()
|
||||
login = await self.mt5.login()
|
||||
if not login:
|
||||
logger.critical("Unable to sign in to MetaTrder 5 Terminal")
|
||||
raise Exception("Unable to sign in to MetaTrader 5 Terminal")
|
||||
@@ -72,12 +72,38 @@ class Bot:
|
||||
logger.error("%s: Bot initialization failed", err)
|
||||
raise SystemExit
|
||||
|
||||
def add_function(self, *, function: Callable[..., ...], **kwargs: dict):
|
||||
def initialize_sync(self):
|
||||
"""Prepares the bot by signing in to the trading account and initializing the symbols for each strategy.
|
||||
Only strategies with successfully initialized symbols will be added to the executor. Starts the global task queue.
|
||||
|
||||
Raises:
|
||||
SystemExit if sign in was not successful
|
||||
"""
|
||||
try:
|
||||
self.mt5.initialize_sync()
|
||||
login = self.mt5.login_sync()
|
||||
if not login:
|
||||
logger.critical("Unable to sign in to MetaTrder 5 Terminal")
|
||||
raise Exception("Unable to sign in to MetaTrader 5 Terminal")
|
||||
logger.info("Login Successful")
|
||||
self.init_strategies_sync()
|
||||
self.add_coroutine(
|
||||
coroutine=self.config.task_queue.run, on_separate_thread=True
|
||||
)
|
||||
self.add_coroutine(coroutine=self.executor.exit)
|
||||
|
||||
if len(self.executor.strategy_runners) == 0:
|
||||
logger.warning("No strategies were added to the bot")
|
||||
except Exception as err:
|
||||
logger.error("%s: Bot initialization failed", err)
|
||||
raise SystemExit
|
||||
|
||||
def add_function(self, *, function: Callable[..., ...], **kwargs):
|
||||
"""Add a function to the executor.
|
||||
|
||||
Args:
|
||||
function (Callable): A function to be executed
|
||||
**kwargs (dict): Keyword arguments for the function
|
||||
**kwargs: Keyword arguments for the function
|
||||
"""
|
||||
self.executor.add_function(function=function, kwargs=kwargs)
|
||||
|
||||
@@ -103,17 +129,17 @@ class Bot:
|
||||
)
|
||||
|
||||
def execute(self):
|
||||
"""Execute the bot."""
|
||||
asyncio.run(self.start())
|
||||
"""Execute the bot using asyncio.run"""
|
||||
self.initialize_sync()
|
||||
self.executor.execute()
|
||||
|
||||
async def start(self):
|
||||
"""Initialize the bot and execute it. Similar to calling `execute` method but is a coroutine."""
|
||||
"""Initialize the bot and call the executor it."""
|
||||
await self.initialize()
|
||||
await self.executor.execute()
|
||||
self.executor.execute()
|
||||
|
||||
def add_strategy(self, *, strategy: Strategy):
|
||||
"""Add a strategy to the list of strategies.
|
||||
An added strategy will only run if it's symbol was successfully initialized and it is added to the executor.
|
||||
|
||||
Args:
|
||||
strategy (Strategy): A Strategy instance to run on bot
|
||||
@@ -163,3 +189,28 @@ class Bot:
|
||||
"""Initialize the symbols for the current trading session. This method is called internally by the bot."""
|
||||
tasks = [self.init_strategy(strategy=strategy) for strategy in self.strategies]
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
def init_strategy_sync(self, *, strategy: Strategy) -> bool:
|
||||
"""Initialize a single strategy. This method is called internally by the bot."""
|
||||
try:
|
||||
select = self.mt5._symbol_select(strategy.symbol.name, True)
|
||||
info = self.mt5._symbol_info(strategy.symbol.name)
|
||||
tick = self.mt5._symbol_info_tick(strategy.symbol.name)
|
||||
self.mt5._market_book_add(strategy.symbol.name)
|
||||
if info is not None and tick is not None:
|
||||
info = info._asdict()
|
||||
info["swap_rollover3days"] = info.get("swap_rollover3days", 0) % 7
|
||||
info['select'] = select
|
||||
tick = Tick(**tick._asdict())
|
||||
strategy.symbol.tick = tick
|
||||
strategy.symbol.set_attributes(**info)
|
||||
self.executor.add_strategy(strategy=strategy)
|
||||
return True
|
||||
return False
|
||||
except Exception as err:
|
||||
logger.warning("%s: Unable to initialize strategy", err)
|
||||
return False
|
||||
|
||||
def init_strategies_sync(self):
|
||||
"""Initialize the symbols for the current trading session. This method is called internally by the bot."""
|
||||
[self.init_strategy_sync(strategy=strategy) for strategy in self.strategies]
|
||||
|
||||
@@ -132,7 +132,7 @@ class Executor:
|
||||
except Exception as err:
|
||||
logger.error(f"Error: {err}. Unable to shutdown executor")
|
||||
|
||||
async def execute(self, *, workers: int = 5):
|
||||
def execute(self, *, workers: int = 5):
|
||||
"""Run the strategies with a threadpool executor.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -123,6 +123,7 @@ class Strategy(ABC):
|
||||
secs = secs - mod if mod != 0 else mod
|
||||
if self.backtest_controller.parties == 2:
|
||||
steps = int(secs) // self.config.backtest_engine.speed
|
||||
steps = max(steps, 1)
|
||||
self.config.backtest_engine.fast_forward(steps=steps)
|
||||
self.backtest_controller.wait()
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ class Symbol(_Base, SymbolInfo):
|
||||
super().__init__(**kwargs)
|
||||
self.account = Account()
|
||||
|
||||
# @backoff_decorator
|
||||
async def info_tick(self, *, name: str = "") -> Tick | None:
|
||||
"""Get the current price tick of a financial instrument.
|
||||
|
||||
@@ -77,7 +76,6 @@ class Symbol(_Base, SymbolInfo):
|
||||
self.select = await self.mt5.symbol_select(self.name, enable)
|
||||
return self.select
|
||||
|
||||
# @backoff_decorator
|
||||
async def info(self) -> SymbolInfo | None:
|
||||
"""Get data on the specified financial instrument and update the symbol object properties
|
||||
|
||||
@@ -104,7 +102,7 @@ class Symbol(_Base, SymbolInfo):
|
||||
info = await self.info()
|
||||
info_tick = await self.info_tick()
|
||||
await self.book_add()
|
||||
if all((info is not None, info_tick is not None)):
|
||||
if info is not None and info_tick is not None:
|
||||
return True
|
||||
logger.warning("Unable to initialize %s", self.name)
|
||||
return False
|
||||
|
||||
@@ -18,7 +18,7 @@ async def test_bot():
|
||||
bot.executor.timeout = 10
|
||||
bot.add_strategies(strategies=strategies)
|
||||
await bot.initialize()
|
||||
await bot.executor.execute()
|
||||
bot.executor.execute()
|
||||
assert len(bot.executor.coroutines) == 1
|
||||
assert len(bot.executor.coroutine_threads) == 1
|
||||
assert bot.config.shutdown is True
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import logging
|
||||
|
||||
from aiomql.lib.bot import Bot
|
||||
from aiomql.contrib.strategies import Chaos
|
||||
from aiomql.contrib.symbols import ForexSymbol
|
||||
|
||||
|
||||
def test_bot_sync():
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
syms = ["BTCUSD", "SOLUSD", "ETHUSD"]
|
||||
symbols = [ForexSymbol(name=sym) for sym in syms]
|
||||
strategies = [Chaos(symbol=symbol, name="test_chaos") for symbol in symbols]
|
||||
bot = Bot()
|
||||
assert bot.config.shutdown is False
|
||||
bot.executor.timeout = 10
|
||||
bot.add_strategies(strategies=strategies)
|
||||
bot.initialize_sync()
|
||||
bot.executor.execute()
|
||||
assert len(bot.executor.strategy_runners) == 3
|
||||
assert len(bot.executor.coroutines) == 1
|
||||
assert len(bot.executor.coroutine_threads) == 1
|
||||
assert bot.config.shutdown is True
|
||||
@@ -9,6 +9,7 @@ class TestBotFactoryAndExecutor:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.bot = Bot()
|
||||
cls.sync_bot = Bot()
|
||||
|
||||
@pytest.fixture(scope="class", autouse=True)
|
||||
async def initialize(self):
|
||||
@@ -18,6 +19,14 @@ class TestBotFactoryAndExecutor:
|
||||
self.bot.add_coroutine(coroutine=self.coro_thread, on_separate_thread=True)
|
||||
await self.bot.initialize()
|
||||
|
||||
@pytest.fixture(scope="class", autouse=True)
|
||||
def initialize_sync(self):
|
||||
self.sync_bot.add_coroutine(coroutine=self.coro_one)
|
||||
self.sync_bot.add_coroutine(coroutine=self.coro_two)
|
||||
self.sync_bot.add_function(function=self.fun_one)
|
||||
self.sync_bot.add_coroutine(coroutine=self.coro_thread, on_separate_thread=True)
|
||||
self.sync_bot.initialize_sync()
|
||||
|
||||
@staticmethod
|
||||
def fun_one():
|
||||
print("function one")
|
||||
@@ -46,4 +55,8 @@ class TestBotFactoryAndExecutor:
|
||||
# task_queue already added coroutine_thread
|
||||
assert len(self.bot.executor.coroutine_threads) == 2
|
||||
|
||||
# def
|
||||
def test_sync_add_workers(self):
|
||||
assert len(self.sync_bot.executor.coroutines) == 3
|
||||
assert len(self.sync_bot.executor.functions) == 1
|
||||
# task_queue already added coroutine_thread
|
||||
assert len(self.sync_bot.executor.coroutine_threads) == 2
|
||||
|
||||
Reference in New Issue
Block a user