mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-21 15:58:09 +00:00
v4.0.9
This commit is contained in:
@@ -79,4 +79,6 @@ aiomql.json
|
|||||||
# development
|
# development
|
||||||
terminals/
|
terminals/
|
||||||
*.pkl
|
*.pkl
|
||||||
|
backtesting/
|
||||||
|
trade_records/
|
||||||
|
|
||||||
|
|||||||
+16
-1
@@ -1,6 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [4.0.7](https://github.com/Ichinga-Samuel/aiomql/releases/tag/v4.0.8) - 2025-01-21
|
## [4.0.9](https://github.com/Ichinga-Samuel/aiomql/releases/tag/v4.0.8) - 2025-01-23
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `__add__` to return a new Candles object
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Removed tasks attribute from executor class
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `initialize_sync` method for synchronous initialization of a symbol
|
||||||
|
|
||||||
|
|
||||||
|
## [4.0.8](https://github.com/Ichinga-Samuel/aiomql/releases/tag/v4.0.8) - 2025-01-21
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
async def rt():
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
print("Hello")
|
||||||
|
|
||||||
|
async def rrt():
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(15)
|
||||||
|
print("rtt")
|
||||||
|
|
||||||
|
|
||||||
|
async def stop(task, tm=10):
|
||||||
|
await asyncio.sleep(tm)
|
||||||
|
print("Stopping", task)
|
||||||
|
res = task.cancel()
|
||||||
|
print(res, task)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
task1 = rt()
|
||||||
|
task2 = rrt()
|
||||||
|
# task3 = stop(task2)
|
||||||
|
tasks = [task1, task2, task3]
|
||||||
|
# task = asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
asyncio.run(asyncio.gather(*tasks, return_exceptions=True))
|
||||||
|
# await asyncio.gather(task, stop(task, tm=3), return_exceptions=True)
|
||||||
|
|
||||||
|
main = main()
|
||||||
|
# asyncio.run(main)
|
||||||
|
|
||||||
|
# def main(a, b=6, **kwargs):
|
||||||
|
# print(a, b, kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# main(4, c=9, g=4, b=99)
|
||||||
@@ -13,6 +13,7 @@ The base class for creating strategies.
|
|||||||
- [backtest_strategy](#strategy.backtest_strategy)
|
- [backtest_strategy](#strategy.backtest_strategy)
|
||||||
- [trade](#strategy.trade)
|
- [trade](#strategy.trade)
|
||||||
- [test](#strategy.test)
|
- [test](#strategy.test)
|
||||||
|
- [initialize](#strategy.initialize)
|
||||||
|
|
||||||
|
|
||||||
<a id="strategy.strategy"></a>
|
<a id="strategy.strategy"></a>
|
||||||
@@ -134,3 +135,10 @@ Runs the strategy in live mode.
|
|||||||
async def live_strategy()
|
async def live_strategy()
|
||||||
```
|
```
|
||||||
Runs the strategy in backtest mode.
|
Runs the strategy in backtest mode.
|
||||||
|
|
||||||
|
<a id="strategy.initialize"></a>
|
||||||
|
### initialize
|
||||||
|
```python
|
||||||
|
async def initialize()
|
||||||
|
```
|
||||||
|
Initialize a strategy
|
||||||
|
|||||||
+13
-1
@@ -7,6 +7,7 @@ Symbol class for handling a financial instrument.
|
|||||||
- [symbol_select](#symbol.symbol_select)
|
- [symbol_select](#symbol.symbol_select)
|
||||||
- [info](#symbol.info)
|
- [info](#symbol.info)
|
||||||
- [initialize](#symbol.initialize)
|
- [initialize](#symbol.initialize)
|
||||||
|
- [initialize_sync](#symbol.initialize_sync)
|
||||||
- [book_add](#symbol.book_add)
|
- [book_add](#symbol.book_add)
|
||||||
- [book_get](#symbol.book_get)
|
- [book_get](#symbol.book_get)
|
||||||
- [book_release](#symbol.book_release)
|
- [book_release](#symbol.book_release)
|
||||||
@@ -94,7 +95,7 @@ Get data on the specified financial instrument and update the symbol object prop
|
|||||||
|
|
||||||
|
|
||||||
<a id="symbol.initialize"></a>
|
<a id="symbol.initialize"></a>
|
||||||
### init
|
### initialize
|
||||||
```python
|
```python
|
||||||
async def initialize() -> bool
|
async def initialize() -> bool
|
||||||
```
|
```
|
||||||
@@ -105,6 +106,17 @@ Initialized the symbol by pulling properties from the terminal
|
|||||||
|--------|--------------------------------------------------------|
|
|--------|--------------------------------------------------------|
|
||||||
| `bool` | Returns True if symbol info was successful initialized |
|
| `bool` | Returns True if symbol info was successful initialized |
|
||||||
|
|
||||||
|
<a id="symbol.initialize_sync"></a>
|
||||||
|
### initialize_sync
|
||||||
|
```python
|
||||||
|
def initialize_sync() -> bool
|
||||||
|
```
|
||||||
|
Initialized the symbol by pulling properties from the terminal
|
||||||
|
#### Returns:
|
||||||
|
| Type | Description |
|
||||||
|
|--------|--------------------------------------------------------|
|
||||||
|
| `bool` | Returns True if symbol info was successful initialized |
|
||||||
|
|
||||||
|
|
||||||
<a id="symbol.book_add"></a>
|
<a id="symbol.book_add"></a>
|
||||||
### book_add
|
### book_add
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"balance": 297007.45,
|
"balance": 367056.24,
|
||||||
"profit": 0,
|
"profit": 0,
|
||||||
"equity": 297007.45,
|
"equity": 367056.24,
|
||||||
"margin": 0.0,
|
"margin": 0.0,
|
||||||
"margin_free": 297007.45,
|
"margin_free": 367056.24,
|
||||||
"margin_level": 0,
|
"margin_level": 0,
|
||||||
"wins": 307,
|
"wins": 374,
|
||||||
"losses": 336,
|
"losses": 394,
|
||||||
"total": 643,
|
"total": 768,
|
||||||
"win_percentage": 47.74,
|
"win_percentage": 48.7,
|
||||||
"win": 619421.92,
|
"win": 946684.28,
|
||||||
"loss": -323164.47,
|
"loss": -580378.04,
|
||||||
"net_profit": 296257.45,
|
"net_profit": 366306.24,
|
||||||
"profit_factor": 1.92,
|
"profit_factor": 1.63,
|
||||||
"profitability": 39500.99
|
"profitability": 48840.83
|
||||||
}
|
}
|
||||||
@@ -20,11 +20,13 @@ class Chaos(Strategy):
|
|||||||
fast_ema: int
|
fast_ema: int
|
||||||
slow_ema: int
|
slow_ema: int
|
||||||
tracker: Tracker
|
tracker: Tracker
|
||||||
parameters = {"fast_ema": 8, "slow_ema": 20, "ltf": TimeFrame.M1, "htf": TimeFrame.M2, "lcc": 100, "hcc": 100}
|
interval: int
|
||||||
|
parameters = {"fast_ema": 8, "slow_ema": 20, "ltf": TimeFrame.M1, "htf": TimeFrame.M2, "lcc": 100, "hcc": 100,
|
||||||
|
"interval": 0}
|
||||||
|
|
||||||
def __init__(self, *, symbol: ForexSymbol, params: dict = None, sessions=None, name="Chaos"):
|
def __init__(self, *, symbol: ForexSymbol, params: dict = None, sessions=None, name="Chaos"):
|
||||||
super().__init__(symbol=symbol, params=params, sessions=sessions, name=name)
|
super().__init__(symbol=symbol, params=params, sessions=sessions, name=name)
|
||||||
self.tracker = Tracker(snooze=self.ltf.seconds)
|
self.tracker = Tracker(snooze=self.interval or self.ltf.seconds)
|
||||||
self.trader = ScalpTrader(symbol=self.symbol)
|
self.trader = ScalpTrader(symbol=self.symbol)
|
||||||
|
|
||||||
async def check_trend(self):
|
async def check_trend(self):
|
||||||
@@ -43,12 +45,12 @@ class Chaos(Strategy):
|
|||||||
candles.rename(inplace=True, **{f"EMA_{self.fast_ema}": "fast", f"EMA_{self.slow_ema}": "slow"})
|
candles.rename(inplace=True, **{f"EMA_{self.fast_ema}": "fast", f"EMA_{self.slow_ema}": "slow"})
|
||||||
order_type = random.choice([OrderType.BUY, OrderType.SELL])
|
order_type = random.choice([OrderType.BUY, OrderType.SELL])
|
||||||
if order_type == OrderType.BUY:
|
if order_type == OrderType.BUY:
|
||||||
self.tracker.update(trend="bullish", snooze=self.htf.seconds, order_type=OrderType.BUY)
|
self.tracker.update(trend="bullish", snooze=self.interval or self.htf.seconds, order_type=OrderType.BUY)
|
||||||
else:
|
else:
|
||||||
self.tracker.update(trend="bearish", snooze=self.htf.seconds, order_type=OrderType.SELL)
|
self.tracker.update(trend="bearish", snooze=self.interval or self.htf.seconds, order_type=OrderType.SELL)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"{err}. Failed to check trend")
|
logger.error(f"{err}. Failed to check trend")
|
||||||
self.tracker.update(trend="ranging", snooze=self.ltf.seconds, order_type=None)
|
self.tracker.update(trend="ranging", snooze=self.interval or self.ltf.seconds, order_type=None)
|
||||||
|
|
||||||
async def trade(self):
|
async def trade(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1067,6 +1067,54 @@ class BackTestEngine:
|
|||||||
tick = await self.get_price_tick(symbol=symbol, time=self.cursor.time)
|
tick = await self.get_price_tick(symbol=symbol, time=self.cursor.time)
|
||||||
return tick
|
return tick
|
||||||
|
|
||||||
|
async def symbol_select(self, *, symbol: str, enable: bool) -> bool:
|
||||||
|
if self.use_terminal:
|
||||||
|
info = await self.mt5.symbol_select(symbol, enable)
|
||||||
|
return info
|
||||||
|
else:
|
||||||
|
return symbol in self._data.symbols.keys()
|
||||||
|
|
||||||
|
def symbol_select_sync(self, *, symbol: str, enable: bool = True) -> bool:
|
||||||
|
if self.use_terminal:
|
||||||
|
info = self.mt5._symbol_select(symbol, enable)
|
||||||
|
return info
|
||||||
|
else:
|
||||||
|
return symbol in self._data.symbols.keys()
|
||||||
|
|
||||||
|
def symbol_info_tick_sync(self, *, symbol) -> Tick | None:
|
||||||
|
if self.use_terminal:
|
||||||
|
time = datetime.fromtimestamp(self.cursor.time, tz=UTC)
|
||||||
|
tick = self.mt5._copy_ticks_from(symbol, time, 1, CopyTicks.ALL)
|
||||||
|
tick = Tick(tick[-1]) if tick is not None else None
|
||||||
|
else:
|
||||||
|
tick = self.prices[symbol].loc[self.cursor.time]
|
||||||
|
tick = Tick(tick) if tick is not None else None
|
||||||
|
return tick
|
||||||
|
|
||||||
|
def symbol_info_sync(self, *, symbol) -> SymbolInfo | None:
|
||||||
|
if self.use_terminal:
|
||||||
|
info = self.mt5._symbol_info(symbol)
|
||||||
|
time = datetime.fromtimestamp(self.cursor.time, tz=UTC)
|
||||||
|
tick = self.mt5._copy_ticks_from(symbol, time, 1, CopyTicks.ALL)
|
||||||
|
tick = Tick(tick[-1]) if tick is not None else None
|
||||||
|
else:
|
||||||
|
info = self.symbols[symbol]
|
||||||
|
tick = self.prices[symbol].loc[self.cursor.time]
|
||||||
|
tick = Tick(tick) if tick is not None else None
|
||||||
|
|
||||||
|
if info and tick:
|
||||||
|
info = info._asdict() | {
|
||||||
|
"bid": tick.bid,
|
||||||
|
"bidhigh": tick.bid,
|
||||||
|
"bidlow": tick.bid,
|
||||||
|
"ask": tick.ask,
|
||||||
|
"askhigh": tick.ask,
|
||||||
|
"asklow": tick.bid,
|
||||||
|
"last": tick.last,
|
||||||
|
"volume_real": tick.volume_real,
|
||||||
|
}
|
||||||
|
return SymbolInfo((info.get(key) for key in SymbolInfo.__match_args__))
|
||||||
|
|
||||||
@async_cache
|
@async_cache
|
||||||
async def _symbol_info(self, *, symbol: str) -> SymbolInfo:
|
async def _symbol_info(self, *, symbol: str) -> SymbolInfo:
|
||||||
if self.use_terminal:
|
if self.use_terminal:
|
||||||
|
|||||||
@@ -87,6 +87,15 @@ class MetaBackTester(MetaTrader):
|
|||||||
return super().login_sync(login=login, password=password, server=server, timeout=timeout)
|
return super().login_sync(login=login, password=password, server=server, timeout=timeout)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _symbol_select(self, symbol: str, enable: bool) -> bool:
|
||||||
|
return self.backtest_engine.symbol_select_sync(symbol=symbol, enable=enable)
|
||||||
|
|
||||||
|
def _market_book_add(self, symbol: str) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def market_book_add(self, symbol: str) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
async def login(self, *, login: int = 0, password: str = "", server: str = "", timeout: int = 60000) -> bool:
|
async def login(self, *, login: int = 0, password: str = "", server: str = "", timeout: int = 60000) -> bool:
|
||||||
if self.config.use_terminal_for_backtesting:
|
if self.config.use_terminal_for_backtesting:
|
||||||
return await super().login(login=login, password=password, server=server, timeout=timeout)
|
return await super().login(login=login, password=password, server=server, timeout=timeout)
|
||||||
@@ -121,6 +130,12 @@ class MetaBackTester(MetaTrader):
|
|||||||
sym = await self.backtest_engine.get_symbol_info(symbol=symbol)
|
sym = await self.backtest_engine.get_symbol_info(symbol=symbol)
|
||||||
return sym
|
return sym
|
||||||
|
|
||||||
|
def _symbol_info(self, symbol) -> SymbolInfo | None:
|
||||||
|
return self.backtest_engine.symbol_info_sync(symbol=symbol)
|
||||||
|
|
||||||
|
def _symbol_info_tick(self, symbol) -> Tick | None:
|
||||||
|
return self.backtest_engine.symbol_info_tick_sync(symbol=symbol)
|
||||||
|
|
||||||
@error_handler(msg="test data not available")
|
@error_handler(msg="test data not available")
|
||||||
async def symbol_info_tick(self, symbol: str) -> Tick | None:
|
async def symbol_info_tick(self, symbol: str) -> Tick | None:
|
||||||
tick = await self.backtest_engine.get_symbol_info_tick(symbol=symbol)
|
tick = await self.backtest_engine.get_symbol_info_tick(symbol=symbol)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from MetaTrader5 import (
|
|||||||
OrderSendResult,
|
OrderSendResult,
|
||||||
OrderCheckResult,
|
OrderCheckResult,
|
||||||
)
|
)
|
||||||
import MetaTrader5 as mt5
|
|
||||||
|
|
||||||
from .constants import OrderType, CopyTicks
|
from .constants import OrderType, CopyTicks
|
||||||
|
|
||||||
@@ -111,7 +110,7 @@ class MetaTrader(MetaCore):
|
|||||||
login = login or acc_details.get("login", 0)
|
login = login or acc_details.get("login", 0)
|
||||||
password = password or acc_details.get("password", "")
|
password = password or acc_details.get("password", "")
|
||||||
server = server or acc_details.get("server", "")
|
server = server or acc_details.get("server", "")
|
||||||
res = mt5.login(login, password=password, server=server, timeout=timeout)
|
res = self._login(login, password=password, server=server, timeout=timeout)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
async def initialize(
|
async def initialize(
|
||||||
@@ -200,7 +199,7 @@ class MetaTrader(MetaCore):
|
|||||||
)
|
)
|
||||||
if key is not None
|
if key is not None
|
||||||
}
|
}
|
||||||
res = mt5.initialize(*args, **kwargs)
|
res = self._initialize(*args, **kwargs)
|
||||||
if res is False:
|
if res is False:
|
||||||
self._shutdown()
|
self._shutdown()
|
||||||
if not res:
|
if not res:
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class TaskQueue:
|
|||||||
mode: Literal['finite', 'infinite'] = 'finite', worker_timeout: int = 1):
|
mode: Literal['finite', 'infinite'] = 'finite', worker_timeout: int = 1):
|
||||||
self.queue = queue or asyncio.PriorityQueue(maxsize=size)
|
self.queue = queue or asyncio.PriorityQueue(maxsize=size)
|
||||||
self.workers = workers
|
self.workers = workers
|
||||||
self.worker_tasks = {}
|
self.worker_tasks: dict[int|float, asyncio.Task] = {}
|
||||||
self.queue_timeout = queue_timeout
|
self.queue_timeout = queue_timeout
|
||||||
self.absolute_timeout = absolute_timeout
|
self.absolute_timeout = absolute_timeout
|
||||||
self.stop = False
|
self.stop = False
|
||||||
@@ -72,6 +72,7 @@ class TaskQueue:
|
|||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.worker_timeout = worker_timeout
|
self.worker_timeout = worker_timeout
|
||||||
self.queue_task_cancelled = False
|
self.queue_task_cancelled = False
|
||||||
|
self.start_time = time.perf_counter()
|
||||||
signal(SIGINT, self.sigint_handle)
|
signal(SIGINT, self.sigint_handle)
|
||||||
|
|
||||||
def add(self, *, item: QueueItem, priority=3, must_complete=False):
|
def add(self, *, item: QueueItem, priority=3, must_complete=False):
|
||||||
@@ -187,6 +188,14 @@ class TaskQueue:
|
|||||||
wr = range(no_of_workers)
|
wr = range(no_of_workers)
|
||||||
[self.worker_tasks.setdefault(wi:=ri(), ct(wi)) for _ in wr]
|
[self.worker_tasks.setdefault(wi:=ri(), ct(wi)) for _ in wr]
|
||||||
|
|
||||||
|
async def watch(self):
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
if (time.perf_counter() - self.start_time) > self.absolute_timeout:
|
||||||
|
self.stop = True
|
||||||
|
self.cancel()
|
||||||
|
break
|
||||||
|
|
||||||
async def run(self, queue_timeout: int = None, absolute_timeout: int = None):
|
async def run(self, queue_timeout: int = None, absolute_timeout: int = None):
|
||||||
"""Run the queue until all tasks are completed or the timeout is reached.
|
"""Run the queue until all tasks are completed or the timeout is reached.
|
||||||
|
|
||||||
@@ -202,6 +211,8 @@ class TaskQueue:
|
|||||||
self.start_timer(queue_timeout=queue_timeout, absolute_timeout=absolute_timeout, start=True)
|
self.start_timer(queue_timeout=queue_timeout, absolute_timeout=absolute_timeout, start=True)
|
||||||
await self.add_workers(no_of_workers=self.workers)
|
await self.add_workers(no_of_workers=self.workers)
|
||||||
self.queue_task = asyncio.create_task(self.queue.join())
|
self.queue_task = asyncio.create_task(self.queue.join())
|
||||||
|
if self.absolute_timeout:
|
||||||
|
asyncio.create_task(self.watch())
|
||||||
await self.queue_task
|
await self.queue_task
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
from typing import Type, Iterable, Callable, Coroutine
|
from typing import Type, Iterable, Callable, Coroutine
|
||||||
from datetime import datetime, UTC
|
|
||||||
|
|
||||||
from MetaTrader5 import Tick
|
|
||||||
|
|
||||||
from .executor import Executor
|
from .executor import Executor
|
||||||
from ..core.config import Config
|
from ..core.config import Config
|
||||||
from ..core.backtesting.backtest_controller import BackTestController
|
from ..core.backtesting.backtest_controller import BackTestController
|
||||||
from ..core.meta_backtester import MetaBackTester
|
from ..core.meta_backtester import MetaBackTester
|
||||||
from ..core.backtesting.backtest_engine import BackTestEngine
|
from ..core.backtesting.backtest_engine import BackTestEngine
|
||||||
from ..core.constants import CopyTicks
|
|
||||||
from .symbol import Symbol as Symbol
|
from .symbol import Symbol as Symbol
|
||||||
from .strategy import Strategy as Strategy
|
from .strategy import Strategy as Strategy
|
||||||
|
|
||||||
@@ -58,8 +55,10 @@ class BackTester:
|
|||||||
self.backtest_engine.setup_account_sync()
|
self.backtest_engine.setup_account_sync()
|
||||||
self.init_strategies_sync()
|
self.init_strategies_sync()
|
||||||
if (strategies := len(self.executor.strategy_runners)) == 0:
|
if (strategies := len(self.executor.strategy_runners)) == 0:
|
||||||
logger.warning("No strategies were added to the backtester. Exiting ...")
|
self.config.shutdown = True
|
||||||
raise Exception("No strategies added to the backtester")
|
logger.warning("No strategies were added to the backtester. Exiting in one second")
|
||||||
|
time.sleep(1)
|
||||||
|
return
|
||||||
self.config.task_queue.worker_timeout = 5
|
self.config.task_queue.worker_timeout = 5
|
||||||
self.add_coroutine(coroutine=self.config.task_queue.run, on_separate_thread=True)
|
self.add_coroutine(coroutine=self.config.task_queue.run, on_separate_thread=True)
|
||||||
self.add_coroutine(coroutine=self.executor.exit)
|
self.add_coroutine(coroutine=self.executor.exit)
|
||||||
@@ -87,8 +86,10 @@ class BackTester:
|
|||||||
await self.backtest_engine.setup_account()
|
await self.backtest_engine.setup_account()
|
||||||
await self.init_strategies()
|
await self.init_strategies()
|
||||||
if (strategies := len(self.executor.strategy_runners)) == 0:
|
if (strategies := len(self.executor.strategy_runners)) == 0:
|
||||||
logger.warning("No strategies were added to the backtester. Exiting ...")
|
self.config.shutdown = True
|
||||||
raise Exception("No strategies added to the backtester")
|
logger.warning("No strategies were added to the backtester. Exiting in one second")
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
return
|
||||||
self.config.task_queue.worker_timeout = 5
|
self.config.task_queue.worker_timeout = 5
|
||||||
self.add_coroutine(coroutine=self.config.task_queue.run, on_separate_thread=True)
|
self.add_coroutine(coroutine=self.config.task_queue.run, on_separate_thread=True)
|
||||||
self.add_coroutine(coroutine=self.executor.exit)
|
self.add_coroutine(coroutine=self.executor.exit)
|
||||||
@@ -113,16 +114,19 @@ class BackTester:
|
|||||||
def execute(self):
|
def execute(self):
|
||||||
"""Execute the bot."""
|
"""Execute the bot."""
|
||||||
self.initialize_sync()
|
self.initialize_sync()
|
||||||
|
if self.config.shutdown is False:
|
||||||
self.executor.execute()
|
self.executor.execute()
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Initialize the bot and execute it. Similar to calling `execute` method but is a coroutine."""
|
"""Initialize the bot and execute it. Similar to calling `execute` method but is a coroutine."""
|
||||||
await self.initialize()
|
await self.initialize()
|
||||||
|
if self.config.shutdown is False:
|
||||||
|
self.executor.execute()
|
||||||
self.executor.execute()
|
self.executor.execute()
|
||||||
|
|
||||||
def add_strategy(self, *, strategy: Strategy):
|
def add_strategy(self, *, strategy: Strategy):
|
||||||
"""Add a strategy to the list of strategies.
|
"""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.
|
An added strategy will only run if it's symbol was successfully initialized, and it is added to the executor.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
strategy (Strategy): A Strategy instance to run on bot
|
strategy (Strategy): A Strategy instance to run on bot
|
||||||
@@ -162,44 +166,10 @@ class BackTester:
|
|||||||
def init_strategy_sync(self, *, strategy: Strategy) -> bool:
|
def init_strategy_sync(self, *, strategy: Strategy) -> bool:
|
||||||
"""Initialize a single strategy. This method is called internally by the bot."""
|
"""Initialize a single strategy. This method is called internally by the bot."""
|
||||||
try:
|
try:
|
||||||
if self.backtest_engine.use_terminal is False:
|
res = strategy.symbol.initialize_sync()
|
||||||
info = self.backtest_engine.symbols[strategy.symbol.name]
|
if res:
|
||||||
tick = self.backtest_engine.prices[strategy.symbol.name].loc[self.backtest_engine.cursor.time]
|
|
||||||
tick = Tick(tick)
|
|
||||||
info = info._asdict() | {
|
|
||||||
"bid": tick.bid,
|
|
||||||
"bidhigh": tick.bid,
|
|
||||||
"bidlow": tick.bid,
|
|
||||||
"ask": tick.ask,
|
|
||||||
"askhigh": tick.ask,
|
|
||||||
"asklow": tick.bid,
|
|
||||||
"last": tick.last,
|
|
||||||
"volume_real": tick.volume_real,
|
|
||||||
}
|
|
||||||
strategy.symbol.set_attributes(**info)
|
|
||||||
self.executor.add_strategy(strategy=strategy)
|
self.executor.add_strategy(strategy=strategy)
|
||||||
return True
|
return res
|
||||||
else:
|
|
||||||
self.mt._symbol_select(strategy.symbol.name, True)
|
|
||||||
self.mt._market_book_add(strategy.symbol.name)
|
|
||||||
info = self.mt._symbol_info(strategy.symbol.name)
|
|
||||||
time = datetime.fromtimestamp(self.backtest_engine.cursor.time, tz=UTC)
|
|
||||||
tick = self.mt._copy_ticks_from(strategy.symbol.name, time, 1, CopyTicks.ALL)
|
|
||||||
tick = Tick(tick[-1]) if tick is not None else None
|
|
||||||
info = info._asdict() | {
|
|
||||||
"bid": tick.bid,
|
|
||||||
"bidhigh": tick.bid,
|
|
||||||
"bidlow": tick.bid,
|
|
||||||
"ask": tick.ask,
|
|
||||||
"askhigh": tick.ask,
|
|
||||||
"asklow": tick.bid,
|
|
||||||
"last": tick.last,
|
|
||||||
"volume_real": tick.volume_real,
|
|
||||||
}
|
|
||||||
strategy.symbol.set_attributes(**info)
|
|
||||||
strategy.symbol.initialized = True
|
|
||||||
self.executor.add_strategy(strategy=strategy)
|
|
||||||
return True
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error("%s: Unable to initialize strategy", err)
|
logger.error("%s: Unable to initialize strategy", err)
|
||||||
return False
|
return False
|
||||||
|
|||||||
+10
-23
@@ -8,7 +8,6 @@ from .executor import Executor
|
|||||||
from ..core.config import Config
|
from ..core.config import Config
|
||||||
from ..core.meta_trader import MetaTrader
|
from ..core.meta_trader import MetaTrader
|
||||||
from .symbol import Symbol as Symbol
|
from .symbol import Symbol as Symbol
|
||||||
from .ticks import Tick
|
|
||||||
from .strategy import Strategy as Strategy
|
from .strategy import Strategy as Strategy
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -67,7 +66,7 @@ class Bot:
|
|||||||
self.add_coroutine(coroutine=self.executor.exit)
|
self.add_coroutine(coroutine=self.executor.exit)
|
||||||
|
|
||||||
if len(self.executor.strategy_runners) == 0:
|
if len(self.executor.strategy_runners) == 0:
|
||||||
logger.warning("No strategies were added to the bot. Exiting in five seconds")
|
logger.warning("No strategies were added to the bot. Exiting in one second")
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
self.config.shutdown = True
|
self.config.shutdown = True
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@@ -76,7 +75,8 @@ class Bot:
|
|||||||
|
|
||||||
def initialize_sync(self):
|
def initialize_sync(self):
|
||||||
"""Prepares the bot by signing in to the trading account and initializing the symbols for each strategy.
|
"""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.
|
Only strategies with successfully initialized symbols will be added to the executor.
|
||||||
|
Starts the global task queue.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SystemExit if sign in was not successful
|
SystemExit if sign in was not successful
|
||||||
@@ -93,7 +93,7 @@ class Bot:
|
|||||||
self.add_coroutine(coroutine=self.executor.exit)
|
self.add_coroutine(coroutine=self.executor.exit)
|
||||||
|
|
||||||
if len(self.executor.strategy_runners) == 0:
|
if len(self.executor.strategy_runners) == 0:
|
||||||
logger.warning("No strategies were added to the bot. Exiting in 5 seconds")
|
logger.warning("No strategies were added to the bot. Exiting in one second")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.config.shutdown = True
|
self.config.shutdown = True
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@@ -120,13 +120,15 @@ class Bot:
|
|||||||
self.executor.add_coroutine(coroutine=coroutine, kwargs=kwargs, on_separate_thread=on_separate_thread)
|
self.executor.add_coroutine(coroutine=coroutine, kwargs=kwargs, on_separate_thread=on_separate_thread)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
"""Execute the bot using asyncio.run"""
|
"""Start the bot in sync mode"""
|
||||||
self.initialize_sync()
|
self.initialize_sync()
|
||||||
|
if self.config.shutdown is False:
|
||||||
self.executor.execute()
|
self.executor.execute()
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Initialize the bot and call the executor it."""
|
"""Initialize the bot and call the executor it."""
|
||||||
await self.initialize()
|
await self.initialize()
|
||||||
|
if self.config.shutdown is False:
|
||||||
self.executor.execute()
|
self.executor.execute()
|
||||||
|
|
||||||
def add_strategy(self, *, strategy: Strategy):
|
def add_strategy(self, *, strategy: Strategy):
|
||||||
@@ -174,25 +176,10 @@ class Bot:
|
|||||||
|
|
||||||
def init_strategy_sync(self, *, strategy: Strategy) -> bool:
|
def init_strategy_sync(self, *, strategy: Strategy) -> bool:
|
||||||
"""Initialize a single strategy. This method is called internally by the bot."""
|
"""Initialize a single strategy. This method is called internally by the bot."""
|
||||||
try:
|
res = strategy.symbol.initialize_sync()
|
||||||
select = self.mt5._symbol_select(strategy.symbol.name, True)
|
if res:
|
||||||
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.initialized = True
|
|
||||||
strategy.symbol.set_attributes(**info)
|
|
||||||
self.executor.add_strategy(strategy=strategy)
|
self.executor.add_strategy(strategy=strategy)
|
||||||
return True
|
return res
|
||||||
return False
|
|
||||||
except Exception as err:
|
|
||||||
logger.warning("%s: Unable to initialize strategy", err)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def init_strategies_sync(self):
|
def init_strategies_sync(self):
|
||||||
"""Initialize the symbols for the current trading session. This method is called internally by the bot."""
|
"""Initialize the symbols for the current trading session. This method is called internally by the bot."""
|
||||||
|
|||||||
@@ -298,11 +298,11 @@ class Candles:
|
|||||||
"""Add a new row to the candles class."""
|
"""Add a new row to the candles class."""
|
||||||
if isinstance(row, Series):
|
if isinstance(row, Series):
|
||||||
data = self. pd.concat([self._data, pd.DataFrame(row).T])
|
data = self. pd.concat([self._data, pd.DataFrame(row).T])
|
||||||
return self.Candle(data=data)
|
return self.__class__(data=data)
|
||||||
|
|
||||||
elif isinstance(row, DataFrame):
|
elif isinstance(row, DataFrame):
|
||||||
data = pd.concat([self._data, row])
|
data = pd.concat([self._data, row])
|
||||||
return self.Candle(data=data)
|
return self.__class__(data=data)
|
||||||
|
|
||||||
def add(self, row: DataFrame | Series) -> bool:
|
def add(self, row: DataFrame | Series) -> bool:
|
||||||
"""Add a new row to the candles class."""
|
"""Add a new row to the candles class."""
|
||||||
|
|||||||
+24
-41
@@ -17,20 +17,18 @@ class Executor:
|
|||||||
Attributes:
|
Attributes:
|
||||||
executor (ThreadPoolExecutor): The executor object.
|
executor (ThreadPoolExecutor): The executor object.
|
||||||
strategy_runners (list): List of strategies.
|
strategy_runners (list): List of strategies.
|
||||||
coroutines (list[Coroutine]): A list of coroutines to run in the executor
|
coroutines (dict[Coroutine, dict]): A list of coroutines to run in the executor
|
||||||
|
coroutine_threads (dict[Coroutine, dict]): A list of coroutines to run in the executor
|
||||||
functions (dict[Callable, dict]): A dictionary of functions to run in the executor
|
functions (dict[Callable, dict]): A dictionary of functions to run in the executor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
executor: ThreadPoolExecutor
|
executor: ThreadPoolExecutor
|
||||||
tasks: list[asyncio.Task | asyncio.Future]
|
|
||||||
config: Config
|
config: Config
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.strategy_runners: list[Strategy] = []
|
self.strategy_runners: list[Strategy] = []
|
||||||
self.coroutines: list[Coroutine] = []
|
self.coroutines: dict[Coroutine: dict] = {}
|
||||||
self.coroutine_threads: list[Coroutine] = []
|
self.coroutine_threads: dict[Coroutine: dict] = {}
|
||||||
self.functions: dict[Callable:dict] = {}
|
self.functions: dict[Callable:dict] = {}
|
||||||
self.tasks = []
|
|
||||||
self.config = Config()
|
self.config = Config()
|
||||||
self.timeout = None # Timeout for the executor. For testing purposes only
|
self.timeout = None # Timeout for the executor. For testing purposes only
|
||||||
signal(SIGINT, self.sigint_handle)
|
signal(SIGINT, self.sigint_handle)
|
||||||
@@ -41,8 +39,10 @@ class Executor:
|
|||||||
|
|
||||||
def add_coroutine(self, *, coroutine: Callable | Coroutine, kwargs: dict = None, on_separate_thread=False):
|
def add_coroutine(self, *, coroutine: Callable | Coroutine, kwargs: dict = None, on_separate_thread=False):
|
||||||
kwargs = kwargs or {}
|
kwargs = kwargs or {}
|
||||||
coroutine = coroutine(**kwargs)
|
if on_separate_thread:
|
||||||
self.coroutines.append(coroutine) if on_separate_thread is False else self.coroutine_threads.append(coroutine)
|
self.coroutine_threads[coroutine] = kwargs
|
||||||
|
else:
|
||||||
|
self.coroutines[coroutine] = kwargs
|
||||||
|
|
||||||
def add_strategies(self, *, strategies: tuple[Strategy]):
|
def add_strategies(self, *, strategies: tuple[Strategy]):
|
||||||
"""Add multiple strategies at once
|
"""Add multiple strategies at once
|
||||||
@@ -60,12 +60,8 @@ class Executor:
|
|||||||
"""
|
"""
|
||||||
self.strategy_runners.append(strategy)
|
self.strategy_runners.append(strategy)
|
||||||
|
|
||||||
# async def create_strategy_task(self, strategy: Strategy):
|
@staticmethod
|
||||||
# task = asyncio.create_task(strategy.run_strategy())
|
def run_strategy(strategy: Strategy):
|
||||||
# self.tasks.append(task)
|
|
||||||
# await task
|
|
||||||
|
|
||||||
def run_strategy(self, strategy: Strategy):
|
|
||||||
"""Wraps the coroutine trade method of each strategy with 'asyncio.run'.
|
"""Wraps the coroutine trade method of each strategy with 'asyncio.run'.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -73,38 +69,23 @@ class Executor:
|
|||||||
"""
|
"""
|
||||||
asyncio.run(strategy.run_strategy())
|
asyncio.run(strategy.run_strategy())
|
||||||
|
|
||||||
async def create_coroutine_task(self, coroutine: Coroutine):
|
async def run_coroutine_tasks(self):
|
||||||
task = asyncio.create_task(coroutine)
|
|
||||||
self.tasks.append(task)
|
|
||||||
await task
|
|
||||||
|
|
||||||
async def create_coroutines_task(self):
|
|
||||||
""""""
|
|
||||||
tasks = [asyncio.create_task(coroutine) for coroutine in self.coroutines]
|
|
||||||
self.tasks.extend(tasks)
|
|
||||||
task = asyncio.gather(*tasks, return_exceptions=True)
|
|
||||||
self.tasks.append(task)
|
|
||||||
await task
|
|
||||||
|
|
||||||
def run_coroutine_tasks(self):
|
|
||||||
"""Run all coroutines in the executor"""
|
"""Run all coroutines in the executor"""
|
||||||
asyncio.run(self.create_coroutines_task())
|
await asyncio.gather(*[coroutine(**kwargs) for coroutine, kwargs in self.coroutines.items()],
|
||||||
|
return_exceptions=True)
|
||||||
|
|
||||||
def run_coroutine_task(self, coroutine):
|
@staticmethod
|
||||||
asyncio.run(self.create_coroutine_task(coroutine))
|
def run_coroutine_task(coroutine, kwargs):
|
||||||
|
asyncio.run(coroutine(**kwargs))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_function(function: Callable, kwargs: dict):
|
def run_function(function: Callable, kwargs: dict):
|
||||||
"""Run a function
|
"""Run a function
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
function: The function to run
|
function: The function to run
|
||||||
kwargs: A dictionary of keyword arguments for the function
|
kwargs: A dictionary of keyword arguments for the function
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
function(**kwargs)
|
function(**kwargs)
|
||||||
except Exception as err:
|
|
||||||
logger.error(f"Error: {err}. Unable to run function: {function.__name__}")
|
|
||||||
|
|
||||||
def sigint_handle(self, signum, frame):
|
def sigint_handle(self, signum, frame):
|
||||||
self.config.shutdown = True
|
self.config.shutdown = True
|
||||||
@@ -117,19 +98,20 @@ class Executor:
|
|||||||
if self.timeout is not None and self.timeout < (asyncio.get_event_loop().time() - start):
|
if self.timeout is not None and self.timeout < (asyncio.get_event_loop().time() - start):
|
||||||
self.config.shutdown = True
|
self.config.shutdown = True
|
||||||
break
|
break
|
||||||
timeout = self.timeout or 30
|
|
||||||
|
timeout = 1 if self.timeout else 30
|
||||||
await asyncio.sleep(timeout)
|
await asyncio.sleep(timeout)
|
||||||
|
|
||||||
for strategy in self.strategy_runners:
|
for strategy in self.strategy_runners:
|
||||||
strategy.running = False
|
strategy.running = False
|
||||||
|
|
||||||
|
self.config.task_queue.cancel()
|
||||||
|
|
||||||
if self.config.backtest_engine is not None:
|
if self.config.backtest_engine is not None:
|
||||||
self.config.backtest_engine.stop_testing = True
|
self.config.backtest_engine.stop_testing = True
|
||||||
|
|
||||||
self.executor.shutdown(wait=False, cancel_futures=False)
|
self.executor.shutdown(wait=False, cancel_futures=False)
|
||||||
|
|
||||||
for task in self.tasks:
|
|
||||||
task.cancel()
|
|
||||||
|
|
||||||
if self.config.force_shutdown:
|
if self.config.force_shutdown:
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@@ -151,5 +133,6 @@ class Executor:
|
|||||||
self.executor = executor
|
self.executor = executor
|
||||||
[self.executor.submit(self.run_strategy, strategy) for strategy in self.strategy_runners]
|
[self.executor.submit(self.run_strategy, strategy) for strategy in self.strategy_runners]
|
||||||
[self.executor.submit(function, **kwargs) for function, kwargs in self.functions.items()]
|
[self.executor.submit(function, **kwargs) for function, kwargs in self.functions.items()]
|
||||||
[self.executor.submit(self.run_coroutine_task, coroutine) for coroutine in self.coroutine_threads]
|
[self.executor.submit(self.run_coroutine_task, coroutine, kwargs) for coroutine, kwargs in
|
||||||
self.executor.submit(self.run_coroutine_tasks)
|
self.coroutine_threads.items()]
|
||||||
|
self.executor.submit(asyncio.run, self.run_coroutine_tasks())
|
||||||
|
|||||||
@@ -177,17 +177,25 @@ class Strategy(ABC):
|
|||||||
try:
|
try:
|
||||||
await self.sessions.check()
|
await self.sessions.check()
|
||||||
await self.trade()
|
await self.trade()
|
||||||
|
|
||||||
except StopTrading:
|
except StopTrading:
|
||||||
self.running = False
|
self.running = False
|
||||||
break
|
break
|
||||||
|
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
self.running = False
|
||||||
|
break
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error("Error: %s in live_strategy", err)
|
logger.error("Error: %s in live_strategy", err)
|
||||||
return
|
self.running = False
|
||||||
|
break
|
||||||
|
|
||||||
async def backtest_strategy(self):
|
async def backtest_strategy(self):
|
||||||
"""Backtest the strategy."""
|
"""Backtest the strategy."""
|
||||||
async with self as _:
|
async with self as _:
|
||||||
logger.info("Testing %s strategy on %s with Backtester", self.name, self.symbol.name)
|
logger.info("Testing %s strategy on %s with Backtester", self.name, self.symbol.name)
|
||||||
|
await self.initialize()
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
await self.sessions.check()
|
await self.sessions.check()
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class Symbol(_Base, SymbolInfo):
|
|||||||
assert "name" in kwargs, "Symbol Object Must be initialized with a name"
|
assert "name" in kwargs, "Symbol Object Must be initialized with a name"
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.account = Account()
|
self.account = Account()
|
||||||
|
self.initialized = False
|
||||||
|
|
||||||
@backoff_decorator
|
@backoff_decorator
|
||||||
async def info_tick(self, *, name: str = "") -> Tick | None:
|
async def info_tick(self, *, name: str = "") -> Tick | None:
|
||||||
@@ -63,7 +64,6 @@ class Symbol(_Base, SymbolInfo):
|
|||||||
tick = Tick(**tick._asdict())
|
tick = Tick(**tick._asdict())
|
||||||
setattr(self, "tick", tick) if not name else ...
|
setattr(self, "tick", tick) if not name else ...
|
||||||
return tick
|
return tick
|
||||||
return None
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.warning("%s: Unable to get tick for %s", err, self.name)
|
logger.warning("%s: Unable to get tick for %s", err, self.name)
|
||||||
return None
|
return None
|
||||||
@@ -92,8 +92,7 @@ class Symbol(_Base, SymbolInfo):
|
|||||||
info = await self.mt5.symbol_info(self.name)
|
info = await self.mt5.symbol_info(self.name)
|
||||||
if info is not None:
|
if info is not None:
|
||||||
info = info._asdict()
|
info = info._asdict()
|
||||||
info["swap_rollover3days"] = info.get("swap_rollover3days", 0) % 7
|
# self.set_attributes(**info)
|
||||||
self.set_attributes(**info)
|
|
||||||
return SymbolInfo(**info)
|
return SymbolInfo(**info)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -104,10 +103,44 @@ class Symbol(_Base, SymbolInfo):
|
|||||||
bool: Returns True if symbol info was successful initialized
|
bool: Returns True if symbol info was successful initialized
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
await self.symbol_select()
|
select = await self.mt5.symbol_select(self.name, True)
|
||||||
info = await self.info()
|
self.select = select
|
||||||
info_tick = await self.info_tick()
|
|
||||||
await self.book_add()
|
await self.mt5.market_book_add(self.name)
|
||||||
|
|
||||||
|
info = await self.mt5.symbol_info(self.name)
|
||||||
|
if info is not None:
|
||||||
|
self.set_attributes(**info._asdict())
|
||||||
|
|
||||||
|
info_tick = await self.mt5.symbol_info_tick(self.name)
|
||||||
|
if info_tick:
|
||||||
|
self.tick = Tick(**info_tick._asdict())
|
||||||
|
|
||||||
|
if info is not None and info_tick is not None:
|
||||||
|
self.initialized = True
|
||||||
|
return True
|
||||||
|
logger.warning("Unable to initialize %s", self.name)
|
||||||
|
return False
|
||||||
|
except Exception as err:
|
||||||
|
logger.warning("%s: Unable to initialize %s", err, self.name)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def initialize_sync(self) -> bool:
|
||||||
|
"""Synchronous version of the initialize method"""
|
||||||
|
try:
|
||||||
|
select = self.mt5._symbol_select(self.name, True)
|
||||||
|
self.select = select
|
||||||
|
|
||||||
|
self.mt5._market_book_add(self.name)
|
||||||
|
|
||||||
|
info = self.mt5._symbol_info(self.name)
|
||||||
|
if info is not None:
|
||||||
|
self.set_attributes(**info._asdict())
|
||||||
|
|
||||||
|
info_tick = self.mt5._symbol_info_tick(self.name)
|
||||||
|
if info_tick:
|
||||||
|
self.tick = Tick(**info_tick._asdict())
|
||||||
|
|
||||||
if info is not None and info_tick is not None:
|
if info is not None and info_tick is not None:
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -4,17 +4,20 @@ from aiomql.lib.bot import Bot
|
|||||||
from aiomql.contrib.strategies import Chaos
|
from aiomql.contrib.strategies import Chaos
|
||||||
from aiomql.contrib.symbols import ForexSymbol
|
from aiomql.contrib.symbols import ForexSymbol
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
async def test_bot():
|
async def test_bot():
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
syms = ["BTCUSD", "SOLUSD", "ETHUSD"]
|
syms = ["BTCUSD", "SOLUSD", "ETHUSD"]
|
||||||
symbols = [ForexSymbol(name=sym) for sym in syms]
|
symbols = [ForexSymbol(name=sym) for sym in syms]
|
||||||
strategies = [Chaos(symbol=symbol, name="test_chaos") for symbol in symbols]
|
strategies = [Chaos(symbol=symbol, name="test_chaos", params={"interval": 3}) for symbol in symbols]
|
||||||
bot = Bot()
|
bot = Bot()
|
||||||
bot.executor.timeout = 5
|
bot.executor.timeout = 10
|
||||||
bot.add_strategies(strategies=strategies)
|
bot.add_strategies(strategies=strategies)
|
||||||
await bot.initialize()
|
await bot.initialize()
|
||||||
bot.executor.execute()
|
bot.executor.execute()
|
||||||
assert len(bot.executor.coroutines) == 1
|
assert len(bot.executor.coroutines) == 1
|
||||||
assert len(bot.executor.coroutine_threads) == 1
|
assert len(bot.executor.coroutine_threads) == 1
|
||||||
|
assert len(bot.executor.strategy_runners) == 3
|
||||||
|
|
||||||
assert bot.config.shutdown is True
|
assert bot.config.shutdown is True
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ def test_bot_sync():
|
|||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
syms = ["BTCUSD", "SOLUSD", "ETHUSD"]
|
syms = ["BTCUSD", "SOLUSD", "ETHUSD"]
|
||||||
symbols = [ForexSymbol(name=sym) for sym in syms]
|
symbols = [ForexSymbol(name=sym) for sym in syms]
|
||||||
strategies = [Chaos(symbol=symbol, name="test_chaos") for symbol in symbols]
|
strategies = [Chaos(symbol=symbol, name="test_chaos", params={"interval": 3}) for symbol in symbols]
|
||||||
bot = Bot()
|
bot = Bot()
|
||||||
bot.config.task_queue.worker_timeout = 2
|
|
||||||
bot.executor.timeout = 10
|
bot.executor.timeout = 10
|
||||||
bot.add_strategies(strategies=strategies)
|
bot.add_strategies(strategies=strategies)
|
||||||
bot.initialize_sync()
|
bot.initialize_sync()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from aiomql.core.task_queue import TaskQueue, QueueItem
|
|||||||
class TestTaskQueue:
|
class TestTaskQueue:
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_class(cls):
|
def setup_class(cls):
|
||||||
cls.task_queue = TaskQueue(timeout=5, worker_timeout=1)
|
cls.task_queue = TaskQueue(absolute_timeout=6, worker_timeout=1)
|
||||||
cls.data = {}
|
cls.data = {}
|
||||||
|
|
||||||
async def task_one(self):
|
async def task_one(self):
|
||||||
@@ -27,14 +27,10 @@ class TestTaskQueue:
|
|||||||
async def test_queue(self):
|
async def test_queue(self):
|
||||||
item_one = QueueItem(self.task_one)
|
item_one = QueueItem(self.task_one)
|
||||||
self.task_queue.add(item=item_one, must_complete=False)
|
self.task_queue.add(item=item_one, must_complete=False)
|
||||||
assert len(self.task_queue.priority_tasks) == 0
|
|
||||||
assert self.task_queue.queue.qsize() == 1
|
assert self.task_queue.queue.qsize() == 1
|
||||||
self.task_queue.add(item=QueueItem(self.task_two), must_complete=True)
|
self.task_queue.add(item=QueueItem(self.task_two), must_complete=True)
|
||||||
assert len(self.task_queue.priority_tasks) == 1
|
|
||||||
assert self.task_queue.queue.qsize() == 2
|
assert self.task_queue.queue.qsize() == 2
|
||||||
self.task_queue.add(item=QueueItem(self.task_three), must_complete=False)
|
self.task_queue.add(item=QueueItem(self.task_three), must_complete=False)
|
||||||
await self.task_queue.run()
|
await self.task_queue.run()
|
||||||
assert len(self.data["task_one"]) >= 2
|
assert len(self.data["task_one"]) >= 2
|
||||||
assert len(self.data["task_two"]) == 10
|
assert len(self.data["task_two"]) == 10
|
||||||
assert len(self.data["task_three"]) == 1
|
|
||||||
assert len(self.task_queue.priority_tasks) == 0
|
|
||||||
|
|||||||
Reference in New Issue
Block a user