This commit is contained in:
Ichinga Samuel
2024-11-08 05:41:23 +01:00
parent 4811aece5c
commit d9bd84bc2e
14 changed files with 361 additions and 282 deletions
+6 -77
View File
@@ -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
View File
@@ -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 |
-162
View File
@@ -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
+74
View File
@@ -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()
+2 -6
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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]
+1 -1
View File
@@ -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:
+1
View File
@@ -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()
+1 -3
View File
@@ -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
+1 -1
View File
@@ -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
+25
View File
@@ -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
+14 -1
View File
@@ -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