diff --git a/.gitignore b/.gitignore
index 0f193b4..2674615 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,6 +43,7 @@ htmlcov/
.coverage
.coverage.*
.cache
+.ruff_cache
.pytest_cache
nosetests.xml
coverage.xml
diff --git a/check.py b/check.py
deleted file mode 100644
index cab2017..0000000
--- a/check.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import asyncio
-
-
-def run():
- for i in range(10):
- print('running')
-
-
-async def run_async():
- for i in range(10):
- await asyncio.sleep(1)
- print('running async')
-
-
-async def main():
- await run_async()
- run()
-
-
-asyncio.run(main())
diff --git a/docs/contrib/symbols/forex_symbol.md b/docs/contrib/symbols/forex_symbol.md
new file mode 100644
index 0000000..dee71a4
--- /dev/null
+++ b/docs/contrib/symbols/forex_symbol.md
@@ -0,0 +1,71 @@
+# Table of Contents
+
+* [aiomql.contrib.symbols.forex\_symbol](#aiomql.contrib.symbols.forex_symbol)
+ * [ForexSymbol](#aiomql.contrib.symbols.forex_symbol.ForexSymbol)
+ * [pip](#aiomql.contrib.symbols.forex_symbol.ForexSymbol.pip)
+ * [compute\_points](#aiomql.contrib.symbols.forex_symbol.ForexSymbol.compute_points)
+ * [compute\_volume\_points](#aiomql.contrib.symbols.forex_symbol.ForexSymbol.compute_volume_points)
+
+
+
+# aiomql.contrib.symbols.forex\_symbol
+
+
+
+## ForexSymbol Objects
+
+```python
+class ForexSymbol(Symbol)
+```
+
+Subclass of Symbol for Forex Symbols. Handles the conversion of currency and the computation of stop loss,
+take profit and volume.
+
+
+
+#### pip
+
+```python
+@property
+def pip()
+```
+
+Returns the pip value of the symbol. This is ten times the point value for forex symbols.
+
+**Returns**:
+
+- `float` - The pip value of the symbol.
+
+
+
+#### compute\_points
+
+```python
+def compute_points(*, amount: float, volume: float) -> float
+```
+
+Compute the number of points required for a trade. Given the amount and the volume of the trade.
+
+**Arguments**:
+
+- `amount` _float_ - Amount to trade
+- `volume` _float_ - Volume to trade
+
+
+
+#### compute\_volume\_points
+
+```python
+async def compute_volume_points(*,
+ amount: float,
+ points: float,
+ round_down: bool = False) -> float
+```
+
+Compute the volume required for a trade. Given the amount and the number of points.
+
+**Arguments**:
+
+- `amount` _float_ - Amount to trade
+- `points` _float_ - Number of points
+- `round_down` - round down the computed volume to the nearest step default True
diff --git a/docs/core/meta_trader.md b/docs/core/meta_trader.md
index 12eef11..912a083 100644
--- a/docs/core/meta_trader.md
+++ b/docs/core/meta_trader.md
@@ -38,14 +38,15 @@ The MetaTrader Class provides an asynchronous wrapper around the MetaTrader5 API
- [history\_deals\_total](#history_deals_total)
- [history\_deals\_get](#history_deals_get)
-
+
### MetaTrader
```python
-class MetaTrader(metaclass=BaseMeta)
+class MetaTrader(MetaCore)
```
The MetaTrader class is a wrapper around the MetaTrader terminal.
It provides methods for connecting to the MetaTrader terminal and retrieving data from it.
-#### Attributes
+
+#### Attributes:
| Name | Type | Description | Default |
|-------|-------|--------------------------------------------------------|------------------------|
| error | Error | The last error encountered by the MetaTrader terminal. | Error(1, 'Successful') |
@@ -62,7 +63,7 @@ async def __aenter__() -> 'MetaTrader'
Async context manager entry point.
Initializes the connection to the MetaTrader terminal.
-#### Returns
+#### Returns:
| Type | Description |
|--------------|-------------------------------------|
| `MetaTrader` | An instance of the MetaTrader class |
@@ -621,4 +622,4 @@ Call without parameters. Return closed deals on all symbols
#### Returns
| Type | Description |
|--------------------|----------------------------------------------------|
-| `tuple[TradeDeal]` | A tuple of closed trade deals as TradeDeal objects |
\ No newline at end of file
+| `tuple[TradeDeal]` | A tuple of closed trade deals as TradeDeal objects |
diff --git a/ruff.toml b/ruff.toml
index f60458b..a4248c3 100644
--- a/ruff.toml
+++ b/ruff.toml
@@ -1,4 +1,2 @@
line-length = 150
target-version = "py311"
-[format]
-skip-magic-trailing-comma=true
diff --git a/sample_backtest.py b/sample_backtest.py
index b11fff3..9479cda 100644
--- a/sample_backtest.py
+++ b/sample_backtest.py
@@ -12,16 +12,8 @@ from aiomql.core.backtesting import BackTestEngine
async def back_tester():
config = Config()
config.mode = "backtest"
- logging.basicConfig(
- level=logging.INFO,
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
- )
- syms = [
- "Volatility 75 Index",
- "Volatility 100 Index",
- "Volatility 25 Index",
- "Volatility 10 Index",
- ]
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+ syms = ["Volatility 75 Index", "Volatility 100 Index", "Volatility 25 Index", "Volatility 10 Index"]
symbols = [ForexSymbol(name=sym) for sym in syms]
strategies = [Chaos(symbol=symbol) for symbol in symbols]
start = datetime(2024, 5, 1, tzinfo=UTC)
@@ -35,7 +27,7 @@ async def back_tester():
close_open_positions_on_exit=True,
assign_to_config=True,
preload=True,
- account_info={'balance': 350}
+ account_info={"balance": 350},
)
backtester = BackTester(backtest_engine=back_test_engine)
backtester.add_strategies(strategies=strategies)
diff --git a/sample_bot.py b/sample_bot.py
index e821f98..4deda7c 100644
--- a/sample_bot.py
+++ b/sample_bot.py
@@ -6,10 +6,7 @@ from aiomql.contrib.symbols import ForexSymbol
def chaos_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 = ["Volatility 75 Index", "Volatility 100 Index", "Volatility 50 Index"]
symbols = [ForexSymbol(name=sym) for sym in syms]
strategies = [Chaos(symbol=symbol) for symbol in symbols]
diff --git a/src/aiomql/_utils.py b/src/aiomql/_utils.py
index 51745d2..23f1763 100644
--- a/src/aiomql/_utils.py
+++ b/src/aiomql/_utils.py
@@ -1,4 +1,5 @@
"""Utility functions for aiomql."""
+
import decimal
import random
from functools import wraps, partial
@@ -26,13 +27,9 @@ def dict_to_string(data: dict, multi=False) -> str:
return f"{sep}".join(f"{key}: {value}" for key, value in data.items())
-def backoff_decorator(
- func=None, *, max_retries: int = 2, retries: int = 0, error=""
-) -> callable:
+def backoff_decorator(func=None, *, max_retries: int = 2, retries: int = 0, error="") -> callable:
if func is None:
- return partial(
- backoff_decorator, max_retries=max_retries, retries=retries, error=error
- )
+ return partial(backoff_decorator, max_retries=max_retries, retries=retries, error=error)
@wraps(func)
async def wrapper(*args, **kwargs):
@@ -57,17 +54,9 @@ def backoff_decorator(
return wrapper
-def error_handler(
- func=None, *, msg="", exe=Exception, response=None, log_error_msg=True
-):
+def error_handler(func=None, *, msg="", exe=Exception, response=None, log_error_msg=True):
if func is None:
- return partial(
- error_handler,
- msg=msg,
- exe=exe,
- response=response,
- log_error_msg=log_error_msg,
- )
+ return partial(error_handler, msg=msg, exe=exe, response=response, log_error_msg=log_error_msg)
@wraps(func)
async def wrapper(*args, **kwargs):
@@ -82,17 +71,9 @@ def error_handler(
return wrapper
-def error_handler_sync(
- func=None, *, msg="", exe=Exception, response=None, log_error_msg=True
-):
+def error_handler_sync(func=None, *, msg="", exe=Exception, response=None, log_error_msg=True):
if func is None:
- return partial(
- error_handler,
- msg=msg,
- exe=exe,
- response=response,
- log_error_msg=log_error_msg,
- )
+ return partial(error_handler, msg=msg, exe=exe, response=response, log_error_msg=log_error_msg)
@wraps(func)
def wrapper(*args, **kwargs):
diff --git a/src/aiomql/contrib/candle_patterns/fractals.py b/src/aiomql/contrib/candle_patterns/fractals.py
index b5e131f..013c3d4 100644
--- a/src/aiomql/contrib/candle_patterns/fractals.py
+++ b/src/aiomql/contrib/candle_patterns/fractals.py
@@ -3,21 +3,11 @@ from ...lib.candle import Candle, Candles
def find_bearish_fractal(candles: Candles) -> Candle | None:
for i in range(len(candles) - 3, 1, -1):
- if candles[i].high > max(
- candles[i - 1].high,
- candles[i + 1].high,
- candles[i - 2].high,
- candles[i + 2].high,
- ):
+ if candles[i].high > max(candles[i - 1].high, candles[i + 1].high, candles[i - 2].high, candles[i + 2].high):
return candles[i]
def find_bullish_fractal(candles: Candles) -> Candle | None:
for i in range(len(candles) - 3, 1, -1):
- if candles[i].low < min(
- candles[i - 1].low,
- candles[i + 1].low,
- candles[i - 2].low,
- candles[i + 2].low,
- ):
+ if candles[i].low < min(candles[i - 1].low, candles[i + 1].low, candles[i - 2].low, candles[i + 2].low):
return candles[i]
diff --git a/src/aiomql/contrib/strategies/chaos.py b/src/aiomql/contrib/strategies/chaos.py
index 2f8685a..f19c1c9 100644
--- a/src/aiomql/contrib/strategies/chaos.py
+++ b/src/aiomql/contrib/strategies/chaos.py
@@ -20,70 +20,40 @@ class Chaos(Strategy):
fast_ema: int
slow_ema: int
tracker: Tracker
- parameters = {
- "fast_ema": 8,
- "slow_ema": 20,
- "ltf": TimeFrame.M1,
- "htf": TimeFrame.M2,
- "lcc": 100,
- "hcc": 100,
- }
+ parameters = {"fast_ema": 8, "slow_ema": 20, "ltf": TimeFrame.M1, "htf": TimeFrame.M2, "lcc": 100, "hcc": 100}
- 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)
self.tracker = Tracker(snooze=self.ltf.seconds)
self.trader = ScalpTrader(symbol=self.symbol)
async def check_trend(self):
try:
- candles = await self.symbol.copy_rates_from_pos(
- timeframe=self.htf, count=self.hcc
- )
- if (
- (current := candles[-1])
- and current.time < self.tracker.trend_time
- and current.close == self.tracker.last_trend_price
- ):
+ candles = await self.symbol.copy_rates_from_pos(timeframe=self.htf, count=self.hcc)
+ if (current := candles[-1]) and current.time < self.tracker.trend_time and current.close == self.tracker.last_trend_price:
self.tracker.update(new=False, order_type=None, snooze=5)
return
- self.tracker.update(
- new=True, trend_time=current.time, last_trend_price=current.close
- )
+ self.tracker.update(new=True, trend_time=current.time, last_trend_price=current.close)
candles.ta.ema(length=self.slow_ema, append=True, fillna=0)
candles.ta.ema(length=self.fast_ema, append=True, fillna=0)
- 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])
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.htf.seconds, order_type=OrderType.BUY)
else:
- self.tracker.update(
- trend="bearish", snooze=self.htf.seconds, order_type=OrderType.SELL
- )
+ self.tracker.update(trend="bearish", snooze=self.htf.seconds, order_type=OrderType.SELL)
except Exception as err:
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.ltf.seconds, order_type=None)
async def trade(self):
try:
await self.check_trend()
if self.tracker.order_type is not None:
- await self.trader.place_trade(
- order_type=self.tracker.order_type, parameters=self.parameters
- )
+ await self.trader.place_trade(order_type=self.tracker.order_type, parameters=self.parameters)
self.tracker.update(order_type=None)
await self.sleep(secs=self.tracker.snooze)
else:
await self.sleep(secs=self.tracker.snooze)
except Exception as err:
- logger.error(
- f"{err}. Failed to trade {self.symbol.name} with {self.__class__.__name__}"
- )
+ logger.error(f"{err}. Failed to trade {self.symbol.name} with {self.__class__.__name__}")
diff --git a/src/aiomql/contrib/strategies/finger_trap.py b/src/aiomql/contrib/strategies/finger_trap.py
index 63fe1ed..29c4036 100644
--- a/src/aiomql/contrib/strategies/finger_trap.py
+++ b/src/aiomql/contrib/strategies/finger_trap.py
@@ -24,46 +24,23 @@ class FingerTrap(Strategy):
tcc: int
trader: Trader
tracker: Tracker
- parameters = {
- "fast_ema": 8,
- "slow_ema": 20,
- "etf": TimeFrame.M5,
- "ttf": TimeFrame.H1,
- "entry_ema": 5,
- "tcc": 672,
- "ecc": 3360,
- }
+ parameters = {"fast_ema": 8, "slow_ema": 20, "etf": TimeFrame.M5, "ttf": TimeFrame.H1, "entry_ema": 5, "tcc": 672, "ecc": 3360}
- def __init__(
- self,
- *,
- symbol: Symbol,
- params: dict | None = None,
- trader: Trader = None,
- sessions: Sessions = None,
- name: str = "FingerTrap",
- ):
+ def __init__(self, *, symbol: Symbol, params: dict | None = None, trader: Trader = None, sessions: Sessions = None, name: str = "FingerTrap"):
super().__init__(symbol=symbol, params=params, sessions=sessions, name=name)
self.trader = trader or SimpleTrader(symbol=self.symbol)
self.tracker: Tracker = Tracker(snooze=self.ttf.seconds)
async def check_trend(self):
try:
- candles: Candles = await self.symbol.copy_rates_from_pos(
- timeframe=self.ttf, count=self.tcc
- )
+ candles: Candles = await self.symbol.copy_rates_from_pos(timeframe=self.ttf, count=self.tcc)
if (current := candles[-1]) and current.time < self.tracker.trend_time:
self.tracker.update(new=False, order_type=None)
return
- self.tracker.update(
- new=True, trend_time=current.time, last_trend_price=current.close
- )
+ self.tracker.update(new=True, trend_time=current.time, last_trend_price=current.close)
candles.ta.ema(length=self.slow_ema, append=True, fillna=0)
candles.ta.ema(length=self.fast_ema, append=True, fillna=0)
- 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"})
fas = candles.ta_lib.above(candles.fast, candles.slow)
fbs = candles.ta_lib.below(candles.fast, candles.slow)
@@ -75,52 +52,34 @@ class FingerTrap(Strategy):
elif fbs.iloc[-1] and cbf.iloc[-1] and current.is_bearish():
self.tracker.update(trend="bearish")
else:
- self.tracker.update(
- trend="ranging", snooze=self.ttf.seconds, order_type=None
- )
+ self.tracker.update(trend="ranging", snooze=self.ttf.seconds, order_type=None)
self.tracker.update(trend="bullish") # remove this line
except Exception as err:
- logger.error(
- f"{err} for {self.symbol} in {self.__class__.__name__}.check_trend"
- )
+ logger.error(f"{err} for {self.symbol} in {self.__class__.__name__}.check_trend")
self.tracker.update(snooze=self.ttf.seconds, order_type=None)
async def confirm_trend(self):
try:
- candles = await self.symbol.copy_rates_from_pos(
- timeframe=self.etf, count=self.ecc
- )
+ candles = await self.symbol.copy_rates_from_pos(timeframe=self.etf, count=self.ecc)
if (current := candles[-1]) and current.time < self.tracker.entry_time:
self.tracker.update(new=False, order_type=None)
return
- self.tracker.update(
- new=True, trend_time=current.time, last_entry_price=current.close
- )
+ self.tracker.update(new=True, trend_time=current.time, last_entry_price=current.close)
candles.ta.ema(length=self.entry_ema, append=True)
candles.rename(**{f"EMA_{self.entry_ema}": "ema"})
candles["cae"] = candles.ta_lib.cross(candles.close, candles.ema)
- candles["cbe"] = candles.ta_lib.cross(
- candles.close, candles.ema, above=False
- )
+ candles["cbe"] = candles.ta_lib.cross(candles.close, candles.ema, above=False)
current = candles[-1]
- if (
- self.tracker.bullish and True or current.cae
- ): # change True to current.cae
+ if self.tracker.bullish and True or current.cae: # change True to current.cae
sl = find_bullish_fractal(candles).low
- self.tracker.update(
- snooze=self.ttf.seconds, order_type=OrderType.BUY, sl=sl
- )
+ self.tracker.update(snooze=self.ttf.seconds, order_type=OrderType.BUY, sl=sl)
elif self.tracker.bearish and current.cbe:
sl = find_bearish_fractal(candles).high
- self.tracker.update(
- snooze=self.ttf.seconds, order_type=OrderType.SELL, sl=sl
- )
+ self.tracker.update(snooze=self.ttf.seconds, order_type=OrderType.SELL, sl=sl)
else:
self.tracker.update(snooze=self.etf.seconds, order_type=None)
except Exception as err:
- logger.error(
- f"{err} for {self.symbol} in {self.__class__.__name__}.confirm_trend"
- )
+ logger.error(f"{err} for {self.symbol} in {self.__class__.__name__}.confirm_trend")
self.tracker.update(snooze=self.etf.seconds, order_type=None)
async def watch_market(self):
@@ -138,11 +97,7 @@ class FingerTrap(Strategy):
if self.tracker.order_type is None:
await self.sleep(secs=self.tracker.snooze)
return
- await self.trader.place_trade(
- order_type=self.tracker.order_type,
- parameters=self.parameters,
- sl=self.tracker.sl,
- )
+ await self.trader.place_trade(order_type=self.tracker.order_type, parameters=self.parameters, sl=self.tracker.sl)
await self.sleep(secs=self.tracker.snooze)
except Exception as err:
logger.error(f"{err} For {self.symbol} in {self.__class__.__name__}.trade")
diff --git a/src/aiomql/contrib/symbols/forex_symbol.py b/src/aiomql/contrib/symbols/forex_symbol.py
index b63abfc..05a2c5d 100644
--- a/src/aiomql/contrib/symbols/forex_symbol.py
+++ b/src/aiomql/contrib/symbols/forex_symbol.py
@@ -24,9 +24,7 @@ class ForexSymbol(Symbol):
points = amount / (volume * self.point * self.trade_contract_size)
return points
- async def compute_volume_points(
- self, *, amount: float, points: float, round_down: bool = False
- ) -> float:
+ async def compute_volume_points(self, *, amount: float, points: float, round_down: bool = False) -> float:
"""Compute the volume required for a trade. Given the amount and the number of points.
Args:
@@ -37,8 +35,6 @@ class ForexSymbol(Symbol):
volume = amount / (self.point * points * self.trade_contract_size)
return self.round_off_volume(volume=volume, round_down=round_down)
- async def compute_volume_sl(
- self, *, amount: float, price: float, sl: float, round_down: bool = False
- ) -> float:
+ async def compute_volume_sl(self, *, amount: float, price: float, sl: float, round_down: bool = False) -> float:
volume = amount / (abs(price - sl) * self.trade_contract_size)
return self.round_off_volume(volume=volume, round_down=round_down)
diff --git a/src/aiomql/contrib/traders/scalp_trader.py b/src/aiomql/contrib/traders/scalp_trader.py
index 3325136..d3a55c1 100644
--- a/src/aiomql/contrib/traders/scalp_trader.py
+++ b/src/aiomql/contrib/traders/scalp_trader.py
@@ -7,9 +7,7 @@ logger = getLogger(__name__)
class ScalpTrader(Trader):
- async def place_trade(
- self, *, order_type: OrderType, volume: float = None, parameters: dict = None
- ):
+ async def place_trade(self, *, order_type: OrderType, volume: float = None, parameters: dict = None):
"""Places a trade based on the order_type and a given stop_loss
Args:
@@ -28,6 +26,4 @@ class ScalpTrader(Trader):
if res is not None:
await self.record_trade(result=res, parameters=self.parameters)
except Exception as err:
- logger.error(
- f"{err} in {self.__class__.__name__}.place_trade for {self.symbol.name}"
- )
+ logger.error(f"{err} in {self.__class__.__name__}.place_trade for {self.symbol.name}")
diff --git a/src/aiomql/contrib/traders/simple_trader.py b/src/aiomql/contrib/traders/simple_trader.py
index 846d17a..0fa2030 100644
--- a/src/aiomql/contrib/traders/simple_trader.py
+++ b/src/aiomql/contrib/traders/simple_trader.py
@@ -7,9 +7,7 @@ logger = getLogger(__name__)
class SimpleTrader(Trader):
- async def place_trade(
- self, *, order_type: OrderType, sl: float, parameters: dict = None
- ):
+ async def place_trade(self, *, order_type: OrderType, sl: float, parameters: dict = None):
"""Places a trade based on the order_type and a given stop_loss
Args:
@@ -25,6 +23,4 @@ class SimpleTrader(Trader):
self.order.comment = self.parameters.get("name", self.__class__.__name__)
await self.send_order()
except Exception as err:
- logger.error(
- f"{err} in {self.__class__.__name__}.place_trade for {self.symbol.name}"
- )
+ logger.error(f"{err} in {self.__class__.__name__}.place_trade for {self.symbol.name}")
diff --git a/src/aiomql/core/_core.py b/src/aiomql/core/_core.py
index 43c3149..c5b8c7f 100644
--- a/src/aiomql/core/_core.py
+++ b/src/aiomql/core/_core.py
@@ -294,21 +294,9 @@ types = (
class BaseMeta(type):
def __new__(mcs, cls_name, bases, cls_dict):
defaults: dict = getattr(MetaTrader5, "__dict__", {})
- callables = {
- f"_{key}": value
- for key in core_mt5_functions
- if (value := defaults.get(key, None)) is not None
- }
- consts = {
- key: value
- for key in constants
- if (value := defaults.get(key, None)) is not None
- }
- types_ = {
- key: value
- for key in types
- if (value := defaults.get(key, None)) is not None
- }
+ callables = {f"_{key}": value for key in core_mt5_functions if (value := defaults.get(key, None)) is not None}
+ consts = {key: value for key in constants if (value := defaults.get(key, None)) is not None}
+ types_ = {key: value for key in types if (value := defaults.get(key, None)) is not None}
cls_dict |= callables
cls_dict |= consts
cls_dict |= types_
diff --git a/src/aiomql/core/backtesting/backtest_controller.py b/src/aiomql/core/backtesting/backtest_controller.py
index 5384834..11f6026 100644
--- a/src/aiomql/core/backtesting/backtest_controller.py
+++ b/src/aiomql/core/backtesting/backtest_controller.py
@@ -52,24 +52,14 @@ class BackTestController:
self.backtest_engine.next()
while True:
pending = self.wait()
- if (
- pending == 0
- ): # all main tasks have been completed in the current cycle
+ if pending == 0: # all main tasks have been completed in the current cycle
await self.backtest_engine.tracker()
self.backtest_engine.next()
if self.backtest_engine.cursor.time % 3600 == 0:
- logger.info(
- datetime.strftime(
- datetime.fromtimestamp(
- self.backtest_engine.cursor.time
- ),
- "%Y-%m-%d %H:%M:%S",
- )
- )
+ logger.info(datetime.strftime(datetime.fromtimestamp(self.backtest_engine.cursor.time), "%Y-%m-%d %H:%M:%S"))
if self.backtest_engine.stop_testing:
logger.info(
- "Stop trading called in control at %s",
- datetime.fromtimestamp(self.backtest_engine.cursor.time).strftime("%Y-%m-%d %H:%M:%S"),
+ "Stop trading called in control at %s", datetime.fromtimestamp(self.backtest_engine.cursor.time).strftime("%Y-%m-%d %H:%M:%S")
)
break
await self.backtest_engine.wrap_up()
diff --git a/src/aiomql/core/backtesting/backtest_engine.py b/src/aiomql/core/backtesting/backtest_engine.py
index 0b8ce0e..7bbfec3 100644
--- a/src/aiomql/core/backtesting/backtest_engine.py
+++ b/src/aiomql/core/backtesting/backtest_engine.py
@@ -25,26 +25,9 @@ from MetaTrader5 import (
)
from ..meta_trader import MetaTrader
-from ..constants import (
- TimeFrame,
- OrderType,
- TradeAction,
- AccountStopOutMode,
- PositionReason,
- DealType,
- DealReason,
- DealEntry,
- OrderReason,
- CopyTicks,
-)
+from ..constants import TimeFrame, OrderType, TradeAction, AccountStopOutMode, PositionReason, DealType, DealReason, DealEntry, OrderReason, CopyTicks
-from ..._utils import (
- round_down,
- round_up,
- error_handler,
- error_handler_sync,
- async_cache,
-)
+from ..._utils import round_down, round_up, error_handler, error_handler_sync, async_cache
from .get_data import BackTestData, GetData, Cursor
from .backtest_account import BackTestAccount
@@ -76,6 +59,7 @@ class BackTestEngine:
preload: bool
account_lock: RLock
account_info: dict
+
def __init__(
self,
*,
@@ -90,7 +74,7 @@ class BackTestEngine:
close_open_positions_on_exit: bool = True,
preload=True,
assign_to_config: bool = True,
- account_info: dict = None
+ account_info: dict = None,
):
self._data = data or BackTestData()
self.mt5 = MetaTrader()
@@ -99,30 +83,14 @@ class BackTestEngine:
self.config.backtest_engine = self
self.setup_test_range(start=start, end=end, speed=speed, restart=restart)
self.setup_data(restart=restart)
- start, end = (
- (self.span[0], self.span[-1])
- if len(self.span) >= 2
- else ((now := datetime.now(UTC).timestamp()), now)
- )
- start, end = datetime.fromtimestamp(start, tz=UTC), datetime.fromtimestamp(
- end, tz=UTC
- )
- self.name = (
- name or self._data.name or f"backtest_data_{start:%d_%m_%y}_{end:%d_%m_%y}"
- )
+ start, end = (self.span[0], self.span[-1]) if len(self.span) >= 2 else ((now := datetime.now(UTC).timestamp()), now)
+ start, end = datetime.fromtimestamp(start, tz=UTC), datetime.fromtimestamp(end, tz=UTC)
+ self.name = name or self._data.name or f"backtest_data_{start:%d_%m_%y}_{end:%d_%m_%y}"
self.stop_testing = False
- self.use_terminal = (
- self.config.use_terminal_for_backtesting
- if use_terminal is None
- else use_terminal
- )
+ self.use_terminal = self.config.use_terminal_for_backtesting if use_terminal is None else use_terminal
self.close_open_positions_on_exit = close_open_positions_on_exit
if stop_time is not None:
- val = (
- stop_time.astimezone(tz=UTC)
- if isinstance(stop_time, datetime)
- else datetime.fromtimestamp(stop_time, tz=UTC)
- )
+ val = stop_time.astimezone(tz=UTC) if isinstance(stop_time, datetime) else datetime.fromtimestamp(stop_time, tz=UTC)
stop_time = int(val.timestamp())
self.stop_time = stop_time
self.preload = preload
@@ -144,27 +112,12 @@ class BackTestEngine:
def __repr__(self):
return f"{self.__class__.__name__}()"
- def setup_test_range(
- self,
- *,
- start: float | datetime = None,
- end: float | datetime = None,
- speed: int = 60,
- restart: bool = True,
- ):
+ def setup_test_range(self, *, start: float | datetime = None, end: float | datetime = None, speed: int = 60, restart: bool = True):
if self._data.span and self._data.range:
start = start or self._data.span[0]
end = end or self._data.span[-1] + speed
- start = (
- start.astimezone(tz=UTC)
- if isinstance(start, datetime)
- else datetime.fromtimestamp(start, tz=UTC)
- )
- end = (
- end.astimezone(tz=UTC)
- if isinstance(end, datetime)
- else datetime.fromtimestamp(end, tz=UTC)
- )
+ start = start.astimezone(tz=UTC) if isinstance(start, datetime) else datetime.fromtimestamp(start, tz=UTC)
+ end = end.astimezone(tz=UTC) if isinstance(end, datetime) else datetime.fromtimestamp(end, tz=UTC)
span_start = int(start.timestamp())
span_end = int(end.timestamp())
self.speed = speed
@@ -192,21 +145,13 @@ class BackTestEngine:
orders = {}
for ticket, order in self._data.orders.items():
- orders[ticket] = TradeOrder(
- (order.get(k) for k in TradeOrder.__match_args__)
- )
+ orders[ticket] = TradeOrder((order.get(k) for k in TradeOrder.__match_args__))
self.orders = OrdersManager(data=orders)
positions = {}
for ticket, position in self._data.positions.items():
- positions[ticket] = TradePosition(
- (position.get(k) for k in TradePosition.__match_args__)
- )
- self.positions = PositionsManager(
- data=positions,
- open_positions=self._data.open_positions,
- margins=self._data.margins,
- )
+ positions[ticket] = TradePosition((position.get(k) for k in TradePosition.__match_args__))
+ self.positions = PositionsManager(data=positions, open_positions=self._data.open_positions, margins=self._data.margins)
deals = {}
for ticket, deal in self._data.deals.items():
@@ -229,11 +174,7 @@ class BackTestEngine:
self.setup_data(restart=True)
def go_to(self, *, time: datetime | float):
- time = (
- time.astimezone(tz=UTC)
- if isinstance(time, datetime)
- else datetime.fromtimestamp(time, tz=UTC)
- )
+ time = time.astimezone(tz=UTC) if isinstance(time, datetime) else datetime.fromtimestamp(time, tz=UTC)
time = int(time.timestamp())
steps = time - self.cursor.time
steps = steps // self.speed
@@ -253,10 +194,7 @@ class BackTestEngine:
async def tracker(self):
try:
- pos_tasks = [
- self.check_position(ticket=ticket)
- for ticket in self.positions._open_positions
- ]
+ pos_tasks = [self.check_position(ticket=ticket) for ticket in self.positions._open_positions]
await asyncio.gather(*pos_tasks)
profit = sum(pos.profit for pos in self.positions.open_positions)
self.update_account(profit=profit)
@@ -266,41 +204,16 @@ class BackTestEngine:
@error_handler_sync
def save_result_to_json(self):
- data = self._account.get_dict(
- include={
- "balance",
- "profit",
- "equity",
- "margin",
- "margin_free",
- "margin_level",
- }
- )
- wins = [
- position
- for ticket in self.positions
- if (position := self.positions.get(ticket)).profit > 0
- ]
- losses = [
- position
- for ticket in self.positions
- if (position := self.positions.get(ticket)).profit <= 0
- ]
- win = round(
- sum(position.profit for position in wins), self._account.currency_digits
- )
- loss = round(
- sum(position.profit for position in losses), self._account.currency_digits
- )
+ data = self._account.get_dict(include={"balance", "profit", "equity", "margin", "margin_free", "margin_level"})
+ wins = [position for ticket in self.positions if (position := self.positions.get(ticket)).profit > 0]
+ losses = [position for ticket in self.positions if (position := self.positions.get(ticket)).profit <= 0]
+ win = round(sum(position.profit for position in wins), self._account.currency_digits)
+ loss = round(sum(position.profit for position in losses), self._account.currency_digits)
profit_factor = round(abs(win / loss), 2) if loss != 0 else 0
wins, losses, total = len(wins), len(losses), len(self.positions._data)
win_percentage = round(wins / total * 100, 2) if total > 0 else 0
net_profit = round(win - abs(loss), self._account.currency_digits)
- profitability = (
- net_profit / (self._account.balance - net_profit) * 100
- if net_profit != 0
- else 0
- )
+ profitability = net_profit / (self._account.balance - net_profit) * 100 if net_profit != 0 else 0
profitability = round(profitability, 2)
data.update(
{
@@ -320,10 +233,7 @@ class BackTestEngine:
json.dump(data, file, indent=4)
async def close_all_open(self):
- tasks = [
- self.check_position(ticket=position.ticket)
- for position in self.positions.open_positions
- ]
+ tasks = [self.check_position(ticket=position.ticket) for position in self.positions.open_positions]
await asyncio.gather(*tasks)
for position in self.positions.open_positions:
await self.close_position_manually(ticket=position.ticket)
@@ -370,9 +280,7 @@ class BackTestEngine:
async def get_price_tick(self, *, symbol: str, time: int) -> Tick | None:
try:
if self.use_terminal and self.preload:
- if (
- ticks := self.preloaded_ticks.get(symbol)
- ) is not None and time in ticks.index:
+ if (ticks := self.preloaded_ticks.get(symbol)) is not None and time in ticks.index:
return Tick(ticks.loc[time])
await self.preload_ticks(symbol=symbol)
tick = self.preloaded_ticks[symbol].loc[time]
@@ -429,9 +337,7 @@ class BackTestEngine:
if res:
pos = self.positions.get(ticket)
deal.update({"profit": pos.profit, "volume": pos.volume})
- self.deals[deal["ticket"]] = TradeDeal(
- (deal.get(k, 0) for k in TradeDeal.__match_args__)
- )
+ self.deals[deal["ticket"]] = TradeDeal((deal.get(k, 0) for k in TradeDeal.__match_args__))
case OrderType.SELL:
if tick.ask <= tp or tick.ask >= sl:
@@ -439,23 +345,15 @@ class BackTestEngine:
if res:
pos = self.positions.get(ticket)
deal.update({"profit": pos.profit, "volume": pos.volume})
- self.deals[deal["ticket"]] = TradeDeal(
- (deal.get(k, 0) for k in TradeDeal.__match_args__)
- )
+ self.deals[deal["ticket"]] = TradeDeal((deal.get(k, 0) for k in TradeDeal.__match_args__))
case _:
...
def check_account(self):
account = self._account
- level = (
- account.margin_level
- if account.margin_so_mode == AccountStopOutMode.PERCENT
- else account.margin_so_call
- )
+ level = account.margin_level if account.margin_so_mode == AccountStopOutMode.PERCENT else account.margin_so_call
if level < account.margin_so_call and level != 0 and account.equity < 0:
- logger.critical(
- "Account has burned out!!! Please top up to continue trading"
- )
+ logger.critical("Account has burned out!!! Please top up to continue trading")
self.stop_testing = True
async def check_position(self, *, ticket: int):
@@ -466,22 +364,10 @@ class BackTestEngine:
ticket (int): Position ticket
"""
pos = self.positions[ticket]
- order_type, symbol, volume, price_open, prev_profit = (
- pos.type,
- pos.symbol,
- pos.volume,
- pos.price_open,
- pos.profit,
- )
+ order_type, symbol, volume, price_open, prev_profit = (pos.type, pos.symbol, pos.volume, pos.price_open, pos.profit)
tick = await self.get_price_tick(symbol=symbol, time=self.cursor.time)
price_current = tick.bid if order_type == OrderType.BUY else tick.ask
- profit = await self.order_calc_profit(
- action=order_type,
- symbol=symbol,
- volume=volume,
- price_open=price_open,
- price_close=price_current,
- )
+ profit = await self.order_calc_profit(action=order_type, symbol=symbol, volume=volume, price_open=price_open, price_close=price_current)
kwargs = dict(price_current=price_current, time_update=self.cursor.time)
kwargs.update(profit=profit) if profit is not None else ...
self.positions.update(ticket=pos.ticket, **kwargs)
@@ -497,9 +383,7 @@ class BackTestEngine:
deal_ticket = random.randint(100_000_000, 199_999_999)
time = position.time_update
time_msc = position.time_update_msc
- order_type = (
- OrderType.BUY if position.type == OrderType.SELL else OrderType.SELL
- )
+ order_type = OrderType.BUY if position.type == OrderType.SELL else OrderType.SELL
order = {
"position_id": position.ticket,
"ticket": order_ticket,
@@ -555,14 +439,8 @@ class BackTestEngine:
margin = self.positions.get_margin(ticket=ticket)
self.positions.delete_margin(ticket=ticket)
self.positions.close(ticket=ticket)
- self.orders.update(
- ticket=ticket,
- time_done=self.cursor.time,
- time_done_msc=self.cursor.time * 1000,
- )
- profit = sum(
- [position.profit for position in self.positions.open_positions]
- )
+ self.orders.update(ticket=ticket, time_done=self.cursor.time, time_done_msc=self.cursor.time * 1000)
+ profit = sum([position.profit for position in self.positions.open_positions])
profit = round(profit, self._account.currency_digits)
self.update_account(gain=position.profit, profit=profit, margin=-margin)
return True
@@ -583,58 +461,29 @@ class BackTestEngine:
Returns:
bool: True if the stops are modified successfully, False otherwise
"""
- self.positions.update(
- ticket=ticket,
- sl=sl,
- tp=tp,
- time_update=self.cursor.time,
- time_update_msc=self.cursor.time * 1000,
- )
+ self.positions.update(ticket=ticket, sl=sl, tp=tp, time_update=self.cursor.time, time_update_msc=self.cursor.time * 1000)
return True
- def update_account(
- self, *, profit: float = None, margin: float = 0, gain: float = 0
- ):
+ def update_account(self, *, profit: float = None, margin: float = 0, gain: float = 0):
self.account_lock.acquire()
try:
self._account.balance += round(gain, self._account.currency_digits)
- self._account.profit = (
- round(profit, self._account.currency_digits)
- if profit is not None
- else self._account.profit
- )
+ self._account.profit = round(profit, self._account.currency_digits) if profit is not None else self._account.profit
self._account.equity = self._account.balance + self._account.profit
self._account.margin += round(margin, self._account.currency_digits)
- self._account.margin_free = round(
- self._account.equity - self._account.margin,
- self._account.currency_digits,
- )
- self._account.balance = round(
- self._account.balance, self._account.currency_digits
- )
- self._account.equity = round(
- self._account.equity, self._account.currency_digits
- )
- self._account.margin = round(
- self._account.margin, self._account.currency_digits
- )
- self._account.margin_free = round(
- self._account.margin_free, self._account.currency_digits
- )
- self._account.profit = round(
- self._account.profit, self._account.currency_digits
- )
+ self._account.margin_free = round(self._account.equity - self._account.margin, self._account.currency_digits)
+ self._account.balance = round(self._account.balance, self._account.currency_digits)
+ self._account.equity = round(self._account.equity, self._account.currency_digits)
+ self._account.margin = round(self._account.margin, self._account.currency_digits)
+ self._account.margin_free = round(self._account.margin_free, self._account.currency_digits)
+ self._account.profit = round(self._account.profit, self._account.currency_digits)
if self._account.margin == 0:
self._account.margin_level = 0
else:
mode = self._account.margin_so_mode
level = self._account.equity / self._account.margin * 100
- self._account.margin_level = (
- level
- if mode == AccountStopOutMode.PERCENT
- else self._account.margin_free
- )
+ self._account.margin_level = level if mode == AccountStopOutMode.PERCENT else self._account.margin_free
except Exception as exe:
logger.critical("Error Updating Account: %s", exe)
@@ -696,9 +545,7 @@ class BackTestEngine:
res = pd.DataFrame(res)
res.drop_duplicates(subset=["time"], keep="last", inplace=True)
res.set_index("time", inplace=True, drop=False)
- res = res.reindex(
- self.span, copy=True, method="nearest"
- ) # fill in missing values with NaN
+ res = res.reindex(self.span, copy=True, method="nearest") # fill in missing values with NaN
prices[symbol] = res
return prices
@@ -725,9 +572,7 @@ class BackTestEngine:
def symbols(self) -> dict[str, SymbolInfo]:
symbols = {}
for symbol, info in self._data.symbols.items():
- symbols[symbol] = SymbolInfo(
- (info.get(key) for key in SymbolInfo.__match_args__)
- )
+ symbols[symbol] = SymbolInfo((info.get(key) for key in SymbolInfo.__match_args__))
return symbols
@error_handler
@@ -736,52 +581,28 @@ class BackTestEngine:
osr = {
"retcode": 10013,
"comment": "Invalid request",
- "request": TradeRequest(
- request.get(k, (0 if k != "comment" else ""))
- for k in TradeRequest.__match_args__
- ),
+ "request": TradeRequest(request.get(k, (0 if k != "comment" else "")) for k in TradeRequest.__match_args__),
}
- current_tick = await self.get_price_tick(
- symbol=request.get("symbol"), time=self.cursor.time
- )
+ current_tick = await self.get_price_tick(symbol=request.get("symbol"), time=self.cursor.time)
if current_tick is None:
osr["comment"] = "Market is closed"
osr["retcode"] = 10018
- return OrderSendResult(
- (osr.get(k, 0) for k in OrderSendResult.__match_args__)
- )
+ return OrderSendResult((osr.get(k, 0) for k in OrderSendResult.__match_args__))
- trade_order = {
- "external_id": "",
- "comment": "",
- **{k: v for k, v in request.items() if k in TradeOrder.__match_args__},
- }
+ trade_order = {"external_id": "", "comment": "", **{k: v for k, v in request.items() if k in TradeOrder.__match_args__}}
order_type, symbol = request.get("type"), request.get("symbol", "")
action, position_id = request.get("action"), request.get("position")
- sl, tp, volume, symbol = (
- request.get("sl"),
- request.get("tp"),
- request.get("volume"),
- request.get("symbol"),
- )
+ sl, tp, volume, symbol = (request.get("sl"), request.get("tp"), request.get("volume"), request.get("symbol"))
order_type = OrderType(order_type)
current_position = self.positions.get(position_id)
order_ticket = random.randint(800_000_000, 899_999_999)
deal_ticket = random.randint(100_000_000, 199_999_999)
# closing an order by an opposite order using a position ticket and Deal action
- if (
- action == TradeAction.DEAL
- and current_position
- and order_type.opposite == current_position.type
- ):
+ if action == TradeAction.DEAL and current_position and order_type.opposite == current_position.type:
res = await self.close_position(ticket=current_position.ticket)
if res:
- price_current = (
- current_tick.ask
- if order_type == OrderType.BUY
- else current_tick.bid
- )
+ price_current = current_tick.ask if order_type == OrderType.BUY else current_tick.bid
trade_order.update(
{
"position_id": current_position.ticket,
@@ -818,64 +639,30 @@ class BackTestEngine:
"external_id": "",
}
- order = TradeOrder(
- (trade_order.get(k, 0) for k in TradeOrder.__match_args__)
- )
+ order = TradeOrder((trade_order.get(k, 0) for k in TradeOrder.__match_args__))
self.orders[order.ticket] = order
deal = TradeDeal((deal.get(k, 0) for k in TradeDeal.__match_args__))
self.deals[deal.ticket] = deal
- osr.update(
- {
- "comment": "Request completed",
- "retcode": 10009,
- "order": order_ticket,
- "deal": deal_ticket,
- }
- )
- return OrderSendResult(
- (osr.get(k, 0) for k in OrderSendResult.__match_args__)
- )
+ osr.update({"comment": "Request completed", "retcode": 10009, "order": order_ticket, "deal": deal_ticket})
+ return OrderSendResult((osr.get(k, 0) for k in OrderSendResult.__match_args__))
if action == TradeAction.SLTP and current_position:
check = await self.order_check(request=request, use_terminal=use_terminal)
if check.retcode != 0:
- osr = {
- "retcode": check.retcode,
- "comment": check.comment,
- "request": check.request,
- }
- return OrderSendResult(
- (osr.get(k, 0) for k in OrderSendResult.__match_args__)
- )
+ osr = {"retcode": check.retcode, "comment": check.comment, "request": check.request}
+ return OrderSendResult((osr.get(k, 0) for k in OrderSendResult.__match_args__))
res = self.modify_stops(ticket=position_id, sl=sl, tp=tp)
if res:
- osr.update(
- {
- "comment": "Request completed",
- "retcode": 10009,
- "order": order_ticket,
- "deal": deal_ticket,
- }
- )
- return OrderSendResult(
- (osr.get(k, 0) for k in OrderSendResult.__match_args__)
- )
+ osr.update({"comment": "Request completed", "retcode": 10009, "order": order_ticket, "deal": deal_ticket})
+ return OrderSendResult((osr.get(k, 0) for k in OrderSendResult.__match_args__))
if action == TradeAction.DEAL and order_type in (OrderType.BUY, OrderType.SELL):
check = await self.order_check(request=request, use_terminal=use_terminal)
if check.retcode != 0:
- osr = {
- "retcode": check.retcode,
- "comment": check.comment,
- "request": check.request,
- }
- return OrderSendResult(
- (osr.get(k, 0) for k in OrderSendResult.__match_args__)
- )
+ osr = {"retcode": check.retcode, "comment": check.comment, "request": check.request}
+ return OrderSendResult((osr.get(k, 0) for k in OrderSendResult.__match_args__))
- price = (
- current_tick.ask if order_type == OrderType.BUY else current_tick.bid
- )
+ price = current_tick.ask if order_type == OrderType.BUY else current_tick.bid
position = {
"ticket": order_ticket,
"symbol": symbol,
@@ -932,12 +719,8 @@ class BackTestEngine:
}
)
- pos = TradePosition(
- (position.get(k, 0) for k in TradePosition.__match_args__)
- )
- order = TradeOrder(
- (trade_order.get(k, 0) for k in TradeOrder.__match_args__)
- )
+ pos = TradePosition((position.get(k, 0) for k in TradePosition.__match_args__))
+ order = TradeOrder((trade_order.get(k, 0) for k in TradeOrder.__match_args__))
deal = TradeDeal((deal.get(k, 0) for k in TradeDeal.__match_args__))
self.deals[deal_ticket] = deal
self.positions[order.ticket] = pos
@@ -954,23 +737,13 @@ class BackTestEngine:
"deal": deal_ticket,
}
)
- margin = await self.order_calc_margin(
- action=action,
- symbol=symbol,
- volume=volume,
- price=price,
- use_terminal=use_terminal,
- )
+ margin = await self.order_calc_margin(action=action, symbol=symbol, volume=volume, price=price, use_terminal=use_terminal)
self.positions.set_margin(ticket=order_ticket, margin=margin)
self.update_account(margin=margin)
- return OrderSendResult(
- (osr.get(k, 0) for k in OrderSendResult.__match_args__)
- )
+ return OrderSendResult((osr.get(k, 0) for k in OrderSendResult.__match_args__))
@error_handler
- async def order_check(
- self, *, request: dict, use_terminal: bool = False
- ) -> OrderCheckResult:
+ async def order_check(self, *, request: dict, use_terminal: bool = False) -> OrderCheckResult:
use_terminal = self.use_terminal or use_terminal
ocr = {
"retcode": 10013,
@@ -981,69 +754,30 @@ class BackTestEngine:
"margin_free": 0,
"margin_level": 0,
"comment": "Invalid request",
- "request": TradeRequest(
- request.get(k, (0 if k != "comment" else ""))
- for k in TradeRequest.__match_args__
- ),
+ "request": TradeRequest(request.get(k, (0 if k != "comment" else "")) for k in TradeRequest.__match_args__),
}
- action, symbol, volume = (
- request.get("action"),
- request.get("symbol"),
- request.get("volume"),
- )
- price, order_type, position_id = (
- request.get("price"),
- request.get("type"),
- request.get("position"),
- )
+ action, symbol, volume = (request.get("action"), request.get("symbol"), request.get("volume"))
+ price, order_type, position_id = (request.get("price"), request.get("type"), request.get("position"))
# check margin and confirm order can go through for a deal action and buy or sell order type
- if (
- action == TradeAction.DEAL
- and order_type in (OrderType.BUY, OrderType.SELL)
- and position_id is None
- ):
- margin = await self.order_calc_margin(
- action=action,
- symbol=symbol,
- volume=volume,
- price=price,
- use_terminal=use_terminal,
- )
+ if action == TradeAction.DEAL and order_type in (OrderType.BUY, OrderType.SELL) and position_id is None:
+ margin = await self.order_calc_margin(action=action, symbol=symbol, volume=volume, price=price, use_terminal=use_terminal)
if margin is None:
- return OrderCheckResult(
- (ocr.get(k, 0) for k in OrderCheckResult.__match_args__)
- )
+ return OrderCheckResult((ocr.get(k, 0) for k in OrderCheckResult.__match_args__))
used_margin = self._account.margin + margin
free_margin = self._account.margin_free - margin
- level = (
- self._account.equity / used_margin * 100
- if used_margin
- else float("inf")
- )
- margin_level = (
- level
- if self._account.margin_so_mode == AccountStopOutMode.PERCENT
- else free_margin
- )
- ocr.update(
- {
- "margin_level": margin_level,
- "margin": margin,
- "margin_free": free_margin,
- }
- )
+ level = self._account.equity / used_margin * 100 if used_margin else float("inf")
+ margin_level = level if self._account.margin_so_mode == AccountStopOutMode.PERCENT else free_margin
+ ocr.update({"margin_level": margin_level, "margin": margin, "margin_free": free_margin})
# check if the account has enough money
if margin_level < self._account.margin_so_call:
ocr["retcode"] = 10019
ocr["comment"] = "No money"
- return OrderCheckResult(
- (ocr.get(k, 0) for k in OrderCheckResult.__match_args__)
- )
+ return OrderCheckResult((ocr.get(k, 0) for k in OrderCheckResult.__match_args__))
# check if the stops level is valid
sym = await self.get_symbol_info(symbol=symbol)
@@ -1053,12 +787,8 @@ class BackTestEngine:
if action == TradeAction.SLTP:
pos = self.positions.get(request.get("position"))
sym = await self.get_symbol_info(symbol=pos.symbol)
- current_tick = sym or await self.get_price_tick(
- pos.symbol, self.cursor.time
- )
- current_price = (
- current_tick.bid if pos.type == OrderType.BUY else current_tick.ask
- )
+ current_tick = sym or await self.get_price_tick(pos.symbol, self.cursor.time)
+ current_price = current_tick.bid if pos.type == OrderType.BUY else current_tick.ask
min_sl = min(sl, tp)
dsl = abs(current_price - min_sl) / sym.point
@@ -1066,46 +796,29 @@ class BackTestEngine:
if dsl < tsl:
ocr["retcode"] = 10016
ocr["comment"] = "Invalid stops"
- return OrderCheckResult(
- (ocr.get(k, 0) for k in OrderCheckResult.__match_args__)
- )
+ return OrderCheckResult((ocr.get(k, 0) for k in OrderCheckResult.__match_args__))
elif action == TradeAction.SLTP:
ocr["comment"] = "Done"
ocr["retcode"] = 0
- return OrderCheckResult(
- (ocr.get(k, 0) for k in OrderCheckResult.__match_args__)
- )
+ return OrderCheckResult((ocr.get(k, 0) for k in OrderCheckResult.__match_args__))
if use_terminal or self.use_terminal:
ocr_t = await self.mt5.order_check(request)
if ocr_t.retcode in (10013, 10014):
return ocr_t
- elif action == TradeAction.DEAL and order_type in (
- OrderType.BUY,
- OrderType.SELL,
- ):
+ elif action == TradeAction.DEAL and order_type in (OrderType.BUY, OrderType.SELL):
# check volume
if volume < sym.volume_min or volume > sym.volume_max:
ocr["retcode"] = 10014
ocr["comment"] = "Invalid volume"
- return OrderCheckResult(
- (ocr.get(k, 0) for k in OrderCheckResult.__match_args__)
- )
+ return OrderCheckResult((ocr.get(k, 0) for k in OrderCheckResult.__match_args__))
ocr.update(
- {
- "balance": self._account.balance,
- "profit": self._account.profit,
- "equity": self._account.equity,
- "comment": "Done",
- "retcode": 0,
- }
+ {"balance": self._account.balance, "profit": self._account.profit, "equity": self._account.equity, "comment": "Done", "retcode": 0}
)
- return OrderCheckResult(
- (ocr.get(k, 0) for k in OrderCheckResult.__match_args__)
- )
+ return OrderCheckResult((ocr.get(k, 0) for k in OrderCheckResult.__match_args__))
@error_handler
async def get_terminal_info(self) -> TerminalInfo:
@@ -1173,19 +886,8 @@ class BackTestEngine:
return SymbolInfo((info.get(key) for key in SymbolInfo.__match_args__))
@error_handler
- async def get_rates_from(
- self,
- *,
- symbol: str,
- timeframe: TimeFrame,
- date_from: datetime | float,
- count: int,
- ) -> np.ndarray:
- date_from = (
- date_from.astimezone(tz=UTC)
- if isinstance(date_from, datetime)
- else datetime.fromtimestamp(date_from, tz=UTC)
- )
+ async def get_rates_from(self, *, symbol: str, timeframe: TimeFrame, date_from: datetime | float, count: int) -> np.ndarray:
+ date_from = date_from.astimezone(tz=UTC) if isinstance(date_from, datetime) else datetime.fromtimestamp(date_from, tz=UTC)
if self.use_terminal:
rates = await self.mt5.copy_rates_from(symbol, timeframe, date_from, count)
return rates
@@ -1194,20 +896,12 @@ class BackTestEngine:
start = int(date_from.timestamp())
start = round_down(start, timeframe.seconds)
rates = rates[rates.time <= start].iloc[-count:]
- return np.fromiter(
- (tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=rates)
- )
+ return np.fromiter((tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=rates))
@error_handler
- async def get_rates_from_pos(
- self, *, symbol: str, timeframe: TimeFrame, start_pos: int, count: int
- ) -> np.ndarray:
+ async def get_rates_from_pos(self, *, symbol: str, timeframe: TimeFrame, start_pos: int, count: int) -> np.ndarray:
if self.use_terminal:
- current_time = (
- self.cursor.time
- if start_pos == 0
- else self.cursor.time - start_pos * timeframe.seconds
- )
+ current_time = self.cursor.time if start_pos == 0 else self.cursor.time - start_pos * timeframe.seconds
current_time = round_up(current_time, timeframe.seconds)
start = datetime.fromtimestamp(current_time, tz=UTC)
rates = await self.mt5.copy_rates_from(symbol, timeframe, start, count)
@@ -1218,63 +912,28 @@ class BackTestEngine:
# the current time rounded up to a multiple of the timeframe in seconds and then subtracted by the start_pos
# multiplied by the timeframe in seconds gives the time of the last candlestick in the range when using
# copy_rates_from_pos
- end = (
- int(round_down(self.cursor.time, timeframe.seconds))
- - start_pos * timeframe.seconds
- )
+ end = int(round_down(self.cursor.time, timeframe.seconds)) - start_pos * timeframe.seconds
start = end - count * timeframe.seconds
rates = rates[(rates.time > start) & (rates.time <= end)]
- return np.fromiter(
- (tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=rates)
- )
+ return np.fromiter((tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=rates))
@error_handler
- async def get_rates_range(
- self,
- *,
- symbol: str,
- timeframe: TimeFrame,
- date_from: datetime | float,
- date_to: datetime | float,
- ) -> np.ndarray:
- date_from = (
- date_from.astimezone(tz=UTC)
- if isinstance(date_from, datetime)
- else datetime.fromtimestamp(date_from, tz=UTC)
- )
- date_to = (
- date_to.astimezone(tz=UTC)
- if isinstance(date_to, datetime)
- else datetime.fromtimestamp(date_to, tz=UTC)
- )
+ async def get_rates_range(self, *, symbol: str, timeframe: TimeFrame, date_from: datetime | float, date_to: datetime | float) -> np.ndarray:
+ date_from = date_from.astimezone(tz=UTC) if isinstance(date_from, datetime) else datetime.fromtimestamp(date_from, tz=UTC)
+ date_to = date_to.astimezone(tz=UTC) if isinstance(date_to, datetime) else datetime.fromtimestamp(date_to, tz=UTC)
if self.use_terminal:
- rates = await self.mt5.copy_rates_range(
- symbol, timeframe, date_from, date_to
- )
+ rates = await self.mt5.copy_rates_range(symbol, timeframe, date_from, date_to)
return rates
rates = self.rates[symbol][timeframe]
start = round_up(int(date_from.timestamp()), timeframe.seconds)
end = round_up(int(date_to.timestamp()), timeframe.seconds)
rates = rates[(rates.time >= start) & (rates.time <= end)]
- return np.fromiter(
- (tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=rates)
- )
+ return np.fromiter((tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=rates))
@error_handler
- async def get_ticks_from(
- self,
- *,
- symbol: str,
- date_from: datetime | float,
- count: int,
- flags: CopyTicks = CopyTicks.ALL,
- ) -> np.ndarray:
- date_from = (
- date_from.astimezone(tz=UTC)
- if isinstance(date_from, datetime)
- else datetime.fromtimestamp(date_from, tz=UTC)
- )
+ async def get_ticks_from(self, *, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks = CopyTicks.ALL) -> np.ndarray:
+ date_from = date_from.astimezone(tz=UTC) if isinstance(date_from, datetime) else datetime.fromtimestamp(date_from, tz=UTC)
if self.use_terminal:
ticks = await self.mt5.copy_ticks_from(symbol, date_from, count, flags)
return ticks
@@ -1282,29 +941,14 @@ class BackTestEngine:
ticks = self.ticks[symbol]
start = int(date_from.timestamp())
rates = ticks[ticks.time <= start].iloc[-count:]
- return np.fromiter(
- (tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=rates)
- )
+ return np.fromiter((tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=rates))
@error_handler
async def get_ticks_range(
- self,
- *,
- symbol: str,
- date_from: datetime | float,
- date_to: datetime | float,
- flags: CopyTicks = CopyTicks.ALL,
+ self, *, symbol: str, date_from: datetime | float, date_to: datetime | float, flags: CopyTicks = CopyTicks.ALL
) -> np.ndarray:
- date_from = (
- date_from.astimezone(tz=UTC)
- if isinstance(date_from, datetime)
- else datetime.fromtimestamp(date_from, tz=UTC)
- )
- date_to = (
- date_to.astimezone(tz=UTC)
- if isinstance(date_to, datetime)
- else datetime.fromtimestamp(date_to, tz=UTC)
- )
+ date_from = date_from.astimezone(tz=UTC) if isinstance(date_from, datetime) else datetime.fromtimestamp(date_from, tz=UTC)
+ date_to = date_to.astimezone(tz=UTC) if isinstance(date_to, datetime) else datetime.fromtimestamp(date_to, tz=UTC)
if self.use_terminal:
ticks = await self.mt5.copy_ticks_range(symbol, date_from, date_to, flags)
return ticks
@@ -1313,19 +957,11 @@ class BackTestEngine:
start = int(date_from.timestamp())
end = int(date_to.timestamp())
rates = ticks[(ticks.time >= start) & (ticks.time <= end)]
- return np.fromiter(
- (tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=ticks)
- )
+ return np.fromiter((tuple(i) for i in rates.iloc), dtype=self.get_dtype(df=ticks))
@error_handler
async def order_calc_margin(
- self,
- *,
- action: Literal[OrderType.BUY, OrderType.SELL],
- symbol: str,
- volume: float,
- price: float,
- use_terminal: bool = None,
+ self, *, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float, price: float, use_terminal: bool = None
):
use_terminal = use_terminal if use_terminal is not None else self.use_terminal
if use_terminal:
@@ -1334,41 +970,22 @@ class BackTestEngine:
sym = self.symbols.get(symbol)
if sym is None and self.use_terminal:
sym = await self._symbol_info(symbol=symbol)
- margin = (volume * sym.trade_contract_size * price) / (
- self._account.leverage / (sym.margin_initial or 1)
- )
+ margin = (volume * sym.trade_contract_size * price) / (self._account.leverage / (sym.margin_initial or 1))
return round(margin, self._account.currency_digits)
@error_handler
async def order_calc_profit(
- self,
- *,
- action: Literal[OrderType.BUY, OrderType.SELL],
- symbol: str,
- volume: float,
- price_open: float,
- price_close: float,
- use_terminal=None,
+ self, *, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float, price_open: float, price_close: float, use_terminal=None
):
use_terminal = use_terminal if use_terminal is not None else self.use_terminal
if use_terminal:
- return await self.mt5.order_calc_profit(
- action, symbol, volume, price_open, price_close
- )
+ return await self.mt5.order_calc_profit(action, symbol, volume, price_open, price_close)
sym = self.symbols.get(symbol)
if sym is None and self.use_terminal:
sym = await self._symbol_info(symbol=symbol)
- profit = (
- volume
- * sym.trade_contract_size
- * (
- (price_close - price_open)
- if action == OrderType.BUY
- else (price_open - price_close)
- )
- )
+ profit = volume * sym.trade_contract_size * ((price_close - price_open) if action == OrderType.BUY else (price_open - price_close))
return round(profit, self._account.currency_digits)
@error_handler_sync
@@ -1382,9 +999,7 @@ class BackTestEngine:
return 0
@error_handler_sync
- def get_orders(
- self, *, symbol: str = "", group: str = "", ticket: int = None
- ) -> tuple[TradeOrder, ...]:
+ def get_orders(self, *, symbol: str = "", group: str = "", ticket: int = None) -> tuple[TradeOrder, ...]:
"""
Get pending orders from the terminal history. This has to do with pending orders, which this backtester
doesn't support yet.
@@ -1412,9 +1027,7 @@ class BackTestEngine:
return self.positions.positions_total()
@error_handler_sync
- def get_positions(
- self, *, symbol: str = None, group: str = None, ticket: int = None
- ) -> tuple[TradePosition, ...]:
+ def get_positions(self, *, symbol: str = None, group: str = None, ticket: int = None) -> tuple[TradePosition, ...]:
"""
Get open positions from the terminal history.
@@ -1429,9 +1042,7 @@ class BackTestEngine:
return self.positions.positions_get(ticket=ticket, symbol=symbol, group=group)
@error_handler_sync
- def get_history_orders_total(
- self, *, date_from: datetime | float, date_to: datetime | float
- ) -> int:
+ def get_history_orders_total(self, *, date_from: datetime | float, date_to: datetime | float) -> int:
"""
Get the total number of orders in the terminal history.
@@ -1447,13 +1058,7 @@ class BackTestEngine:
@error_handler_sync
def get_history_orders(
- self,
- *,
- date_from: datetime | float = None,
- date_to: datetime | float = None,
- group: str = "",
- ticket: int = None,
- position: int = None,
+ self, *, date_from: datetime | float = None, date_to: datetime | float = None, group: str = "", ticket: int = None, position: int = None
) -> tuple[TradeOrder, ...]:
"""
Get orders from the terminal history.
@@ -1468,18 +1073,10 @@ class BackTestEngine:
Returns:
tuple[TradeOrder]: Orders in the history
"""
- return self.orders.history_orders_get(
- date_from=date_from,
- date_to=date_to,
- group=group,
- ticket=ticket,
- position=position,
- )
+ return self.orders.history_orders_get(date_from=date_from, date_to=date_to, group=group, ticket=ticket, position=position)
@error_handler_sync
- def get_history_deals_total(
- self, *, date_from: datetime | float, date_to: datetime | float
- ) -> int:
+ def get_history_deals_total(self, *, date_from: datetime | float, date_to: datetime | float) -> int:
"""
Get the total number of deals in the terminal history.
@@ -1494,13 +1091,7 @@ class BackTestEngine:
@error_handler_sync
def get_history_deals(
- self,
- *,
- date_from: datetime | float = None,
- date_to: datetime | float = None,
- group: str = None,
- position: int = None,
- ticket: int = None,
+ self, *, date_from: datetime | float = None, date_to: datetime | float = None, group: str = None, position: int = None, ticket: int = None
) -> tuple[TradeDeal, ...]:
"""
Get deals from the terminal history.
@@ -1515,10 +1106,4 @@ class BackTestEngine:
Returns:
tuple[TradeDeal, ...]: Deals in the history
"""
- return self.deals.history_deals_get(
- date_from=date_from,
- date_to=date_to,
- group=group,
- position=position,
- ticket=ticket,
- )
+ return self.deals.history_deals_get(date_from=date_from, date_to=date_to, group=group, position=position, ticket=ticket)
diff --git a/src/aiomql/core/backtesting/get_data.py b/src/aiomql/core/backtesting/get_data.py
index e2cdf8b..7b9118e 100644
--- a/src/aiomql/core/backtesting/get_data.py
+++ b/src/aiomql/core/backtesting/get_data.py
@@ -58,15 +58,7 @@ class BackTestData:
class GetData:
data: BackTestData
- def __init__(
- self,
- *,
- start: datetime,
- end: datetime,
- symbols: Sequence[str],
- timeframes: Sequence[TimeFrame],
- name: str = "",
- ):
+ def __init__(self, *, start: datetime, end: datetime, symbols: Sequence[str], timeframes: Sequence[TimeFrame], name: str = ""):
""""""
self.config = Config()
self.start = start.astimezone(tz=UTC)
@@ -102,14 +94,8 @@ class GetData:
logger.error(f"Error: {err}")
def save_data(self, *, name: str | Path = ""):
- name = name or (
- self.name + ".pkl" if not self.name.endswith(".pkl") else self.name
- )
- name = (
- Path(self.config.backtest_dir) / name
- if not isinstance(name, Path)
- else name
- )
+ name = name or (self.name + ".pkl" if not self.name.endswith(".pkl") else self.name)
+ name = Path(self.config.backtest_dir) / name if not isinstance(name, Path) else name
with open(name, "wb") as fo:
pickle.dump(self.data, fo, protocol=pickle.HIGHEST_PROTOCOL)
@@ -118,26 +104,15 @@ class GetData:
if workers:
self.task_queue.workers = workers
- q_items = [
- QueueItem(self.get_symbols_rates),
- QueueItem(self.get_symbols_ticks),
- QueueItem(self.get_symbols_info),
- ]
+ q_items = [QueueItem(self.get_symbols_rates), QueueItem(self.get_symbols_ticks), QueueItem(self.get_symbols_info)]
- [
- self.task_queue.add(item=item, priority=0, must_complete=True)
- for item in q_items
- ]
+ [self.task_queue.add(item=item, priority=0, must_complete=True) for item in q_items]
if not self.data.account:
- self.task_queue.add(
- item=QueueItem(self.get_account_info), must_complete=True
- )
+ self.task_queue.add(item=QueueItem(self.get_account_info), must_complete=True)
if not self.data.terminal:
- self.task_queue.add(
- item=QueueItem(self.get_terminal_info), must_complete=True
- )
+ self.task_queue.add(item=QueueItem(self.get_terminal_info), must_complete=True)
if not self.data.version:
self.task_queue.add(item=QueueItem(self.get_version), must_complete=True)
@@ -146,9 +121,7 @@ class GetData:
if self.data.fully_loaded is False:
logger.warning("Data not fully loaded")
- self.data = BackTestData(
- name=self.name, span=self.span, range=self.range, fully_loaded=False
- )
+ self.data = BackTestData(name=self.name, span=self.span, range=self.range, fully_loaded=False)
async def get_terminal_info(self):
""""""
@@ -179,29 +152,16 @@ class GetData:
async def get_symbols_info(self):
""""""
- [
- self.task_queue.add(item=QueueItem(self.get_symbol_info, symbol=symbol))
- for symbol in self.symbols
- if self.data.symbols.get(symbol) is None
- ]
+ [self.task_queue.add(item=QueueItem(self.get_symbol_info, symbol=symbol)) for symbol in self.symbols if self.data.symbols.get(symbol) is None]
async def get_symbols_ticks(self):
""""""
- [
- self.task_queue.add(item=QueueItem(self.get_symbol_ticks, symbol=symbol))
- for symbol in self.symbols
- if self.data.ticks.get(symbol) is None
- ]
+ [self.task_queue.add(item=QueueItem(self.get_symbol_ticks, symbol=symbol)) for symbol in self.symbols if self.data.ticks.get(symbol) is None]
async def get_symbols_rates(self):
""""""
[
- self.task_queue.add(
- item=QueueItem(
- self.get_symbol_rates, symbol=symbol, timeframe=timeframe
- ),
- priority=4,
- )
+ self.task_queue.add(item=QueueItem(self.get_symbol_rates, symbol=symbol, timeframe=timeframe), priority=4)
for symbol in self.symbols
for timeframe in self.timeframes
if self.data.rates.get(symbol, {}).get(timeframe) is None
@@ -219,9 +179,7 @@ class GetData:
@backoff_decorator
async def get_symbol_ticks(self, *, symbol: str):
""""""
- res = await self.mt5.copy_ticks_range(
- symbol, self.start, self.end, MetaTrader5.COPY_TICKS_ALL
- )
+ res = await self.mt5.copy_ticks_range(symbol, self.start, self.end, MetaTrader5.COPY_TICKS_ALL)
if res is None:
self.data.fully_loaded = False
self.task_queue.stop_queue()
diff --git a/src/aiomql/core/backtesting/trades_manager.py b/src/aiomql/core/backtesting/trades_manager.py
index 27651e2..132f8ca 100644
--- a/src/aiomql/core/backtesting/trades_manager.py
+++ b/src/aiomql/core/backtesting/trades_manager.py
@@ -66,13 +66,9 @@ class PositionsManager(TradeManager):
_open_positions: set[int]
margins: dict[int, float]
- def __init__(
- self, *, data: dict = None, open_positions: set = None, margins: dict = None
- ):
+ def __init__(self, *, data: dict = None, open_positions: set = None, margins: dict = None):
super().__init__(data=data)
- self._open_positions = open_positions or {
- trade.ticket for trade in self._data.values()
- }
+ self._open_positions = open_positions or {trade.ticket for trade in self._data.values()}
self.margins: dict[int, float] = margins or dict()
def __len__(self):
@@ -113,22 +109,12 @@ class PositionsManager(TradeManager):
def set_margin(self, *, ticket: int, margin: float):
self.margins[ticket] = margin
- def positions_get(
- self, *, ticket: int = None, symbol: str = None, group: None = None
- ) -> tuple[TradePosition, ...]:
+ def positions_get(self, *, ticket: int = None, symbol: str = None, group: None = None) -> tuple[TradePosition, ...]:
if ticket:
- return tuple(
- position
- for position in self.open_positions
- if position.ticket == ticket
- )
+ return tuple(position for position in self.open_positions if position.ticket == ticket)
if symbol:
- return tuple(
- position
- for position in self.open_positions
- if position.symbol == symbol
- )
+ return tuple(position for position in self.open_positions if position.symbol == symbol)
if group:
return self.open_positions
@@ -143,33 +129,19 @@ class PositionsManager(TradeManager):
@property
def open_positions(self) -> tuple[TradePosition, ...]:
- return tuple(
- position
- for position in self.values()
- if position.ticket in self._open_positions
- )
+ return tuple(position for position in self.values() if position.ticket in self._open_positions)
class OrdersManager(TradeManager):
_data = dict[int, TradeOrder]
- def get_orders_range(
- self, *, date_from: float, date_to: float
- ) -> tuple[TradeData, ...]:
+ def get_orders_range(self, *, date_from: float, date_to: float) -> tuple[TradeData, ...]:
start = date_from.timestamp() if isinstance(date_from, datetime) else date_from
end = date_to.timestamp() if isinstance(date_to, datetime) else date_to
- return tuple(
- order for order in self.values() if start <= order.time_setup <= end
- )
+ return tuple(order for order in self.values() if start <= order.time_setup <= end)
def history_orders_get(
- self,
- *,
- date_from: float | datetime = None,
- date_to: float | datetime = None,
- group: str = "",
- ticket: int = None,
- position: int = None,
+ self, *, date_from: float | datetime = None, date_to: float | datetime = None, group: str = "", ticket: int = None, position: int = None
) -> tuple[TradeOrder, ...]:
if date_from and date_to:
orders = self.get_orders_range(date_from=date_from, date_to=date_to)
@@ -181,36 +153,24 @@ class OrdersManager(TradeManager):
return tuple(order for order in self.values() if order.ticket == ticket)
if position:
- return tuple(
- order for order in self.values() if order.position_id == position
- )
+ return tuple(order for order in self.values() if order.position_id == position)
return ()
- def history_orders_total(
- self, *, date_from: datetime | float, date_to: datetime | float
- ) -> int:
+ def history_orders_total(self, *, date_from: datetime | float, date_to: datetime | float) -> int:
return len(self.get_orders_range(date_from=date_from, date_to=date_to))
class DealsManager(TradeManager):
_data = dict[int, TradeDeal]
- def get_deals_range(
- self, *, date_from: float, date_to: float
- ) -> tuple[TradeData, ...]:
+ def get_deals_range(self, *, date_from: float, date_to: float) -> tuple[TradeData, ...]:
start = date_from.timestamp() if isinstance(date_from, datetime) else date_from
end = date_to.timestamp() if isinstance(date_to, datetime) else date_to
return tuple(deal for deal in self.values() if start <= deal.time <= end)
def history_deals_get(
- self,
- *,
- date_from: float | datetime = None,
- date_to: float | datetime = None,
- group: str = "",
- ticket: int = None,
- position: int = None,
+ self, *, date_from: float | datetime = None, date_to: float | datetime = None, group: str = "", ticket: int = None, position: int = None
) -> tuple[TradeDeal, ...]:
if date_from and date_to:
deals = self.get_deals_range(date_from=date_from, date_to=date_to)
@@ -226,7 +186,5 @@ class DealsManager(TradeManager):
return ()
- def history_deals_total(
- self, *, date_from: datetime | float, date_to: datetime | float
- ) -> int:
+ def history_deals_total(self, *, date_from: datetime | float, date_to: datetime | float) -> int:
return len(self.get_deals_range(date_from=date_from, date_to=date_to))
diff --git a/src/aiomql/core/base.py b/src/aiomql/core/base.py
index 042514e..813f8ea 100644
--- a/src/aiomql/core/base.py
+++ b/src/aiomql/core/base.py
@@ -24,32 +24,14 @@ class Base:
Args:
**kwargs: Set instance attributes with keyword arguments. Only if they are annotated on the class body.
"""
- self.exclude = {
- "mt5",
- "config",
- "exclude",
- "include",
- "annotations",
- "class_vars",
- "dict",
- "_instance",
- }
+ self.exclude = {"mt5", "config", "exclude", "include", "annotations", "class_vars", "dict", "_instance"}
self.include = set()
self.set_attributes(**kwargs)
def __repr__(self):
- kv = [
- (k, v)
- for k, v in self.__dict__.items()
- if not k.startswith("_")
- and (type(v) in (int, float, str) or isinstance(v, enum.Enum))
- ]
+ kv = [(k, v) for k, v in self.__dict__.items() if not k.startswith("_") and (type(v) in (int, float, str) or isinstance(v, enum.Enum))]
args = ", ".join("%s=%s" % (i, j) for i, j in kv[:3])
- args = (
- args
- if len(kv) <= 3
- else args + " ... " + ", ".join("%s=%s" % (i, j) for i, j in kv[-1:])
- )
+ args = args if len(kv) <= 3 else args + " ... " + ", ".join("%s=%s" % (i, j) for i, j in kv[-1:])
return "%(class)s(%(args)s)" % {"class": self.__class__.__name__, "args": args}
def set_attributes(self, **kwargs):
@@ -68,21 +50,15 @@ class Base:
try:
setattr(self, i, self.annotations[i](j))
except KeyError:
- logger.debug(
- f"Attribute {i} does not belong to class {self.__class__.__name__}"
- )
+ logger.debug(f"Attribute {i} does not belong to class {self.__class__.__name__}")
continue
except (ValueError, TypeError):
- logger.debug(
- f"Cannot covert object of type {type(j)} to type {self.annotations[i]}"
- )
+ logger.debug(f"Cannot covert object of type {type(j)} to type {self.annotations[i]}")
setattr(self, i, j)
except Exception as exe:
- logger.debug(
- f"Did not set attribute {i} on class {self.__class__.__name__} due to {exe}"
- )
+ logger.debug(f"Did not set attribute {i} on class {self.__class__.__name__} due to {exe}")
continue
@property
@@ -113,11 +89,7 @@ class Base:
"""
exclude, include = exclude or set(), include or set()
filter_ = include or set(self.dict.keys()).difference(exclude)
- return {
- key: value
- for key, value in self.dict.items()
- if key in filter_ and value is not None
- }
+ return {key: value for key, value in self.dict.items() if key in filter_ and value is not None}
@property
@cache
@@ -131,9 +103,7 @@ class Base:
cls_dict = {}
for cls in clss:
cls_dict |= cls.__dict__
- return {
- key: value for key, value in cls_dict.items() if key in self.annotations
- }
+ return {key: value for key, value in cls_dict.items() if key in self.annotations}
@property
def dict(self) -> dict:
@@ -144,11 +114,7 @@ class Base:
"""
try:
_filter = self.exclude.difference(self.include)
- return {
- key: value
- for key, value in (self.class_vars | self.__dict__).items()
- if key not in _filter and value is not None
- }
+ return {key: value for key, value in (self.class_vars | self.__dict__).items() if key not in _filter and value is not None}
except Exception as err:
logger.warning(err)
diff --git a/src/aiomql/core/config.py b/src/aiomql/core/config.py
index 00c4950..2b75cf5 100644
--- a/src/aiomql/core/config.py
+++ b/src/aiomql/core/config.py
@@ -114,9 +114,7 @@ class Config:
**kwargs: Object attributes and values as keyword arguments
"""
if kwargs.pop("root", None) is not None:
- logger.warning(
- "Tried setting root from set_attributes. Use load_config to change project root"
- )
+ logger.warning("Tried setting root from set_attributes. Use load_config to change project root")
[setattr(self, key, value) for key, value in kwargs.items()]
@staticmethod
@@ -145,14 +143,7 @@ class Config:
logger.debug(f"Error finding config file: {err}")
return
- def load_config(
- self,
- *,
- file: str | Path = None,
- filename: str = None,
- root: str | Path = None,
- **kwargs,
- ) -> Self:
+ def load_config(self, *, file: str | Path = None, filename: str = None, root: str | Path = None, **kwargs) -> Self:
"""Load configuration settings from a file.
Keyword Args:
@@ -190,17 +181,9 @@ class Config:
self.set_attributes(**data)
if self.path:
- self.path = (
- self.root / self.path
- if not Path(self.path).resolve().exists()
- else self.path
- )
+ self.path = self.root / self.path if not Path(self.path).resolve().exists() else self.path
- if self.record_trades and (
- hasattr(self, "records_dir") is False
- or self.records_dir is None
- or root is not None
- ):
+ if self.record_trades and (hasattr(self, "records_dir") is False or self.records_dir is None or root is not None):
self.records_dir = self.root / self.records_dir_name
self.records_dir.mkdir(parents=True, exist_ok=True)
diff --git a/src/aiomql/core/meta_backtester.py b/src/aiomql/core/meta_backtester.py
index 00a5ada..1f07d92 100644
--- a/src/aiomql/core/meta_backtester.py
+++ b/src/aiomql/core/meta_backtester.py
@@ -3,17 +3,7 @@ from logging import getLogger
from typing import Literal, TypeVar
from numpy import ndarray
-from MetaTrader5 import (
- Tick,
- SymbolInfo,
- AccountInfo,
- TerminalInfo,
- TradeOrder,
- TradePosition,
- TradeDeal,
- OrderCheckResult,
- OrderSendResult,
-)
+from MetaTrader5 import Tick, SymbolInfo, AccountInfo, TerminalInfo, TradeOrder, TradePosition, TradeDeal, OrderCheckResult, OrderSendResult
from .meta_trader import MetaTrader
from .constants import TimeFrame, CopyTicks, OrderType
@@ -49,73 +39,29 @@ class MetaBackTester(MetaTrader):
return await super().last_error()
async def initialize(
- self,
- *,
- path: str = "",
- login: int = 0,
- password: str = "",
- server: str = "",
- timeout: int | None = None,
- portable=False,
+ self, *, path: str = "", login: int = 0, password: str = "", server: str = "", timeout: int | None = None, portable=False
) -> bool:
if self.config.use_terminal_for_backtesting:
- return await super().initialize(
- path=path,
- login=login,
- password=password,
- server=server,
- timeout=timeout,
- )
+ return await super().initialize(path=path, login=login, password=password, server=server, timeout=timeout)
return True
def initialize_sync(
- self,
- *,
- path: str = "",
- login: int = 0,
- password: str = "",
- server: str = "",
- timeout: int | None = None,
- portable=False,
+ self, *, path: str = "", login: int = 0, password: str = "", server: str = "", timeout: int | None = None, portable=False
) -> bool:
if self.config.use_terminal_for_backtesting:
- return super().initialize_sync(
- path=path,
- login=login,
- password=password,
- server=server,
- timeout=timeout,
- )
+ return super().initialize_sync(path=path, login=login, password=password, server=server, timeout=timeout)
return True
- def login_sync(
- self,
- *,
- login: int = 0,
- password: str = "",
- server: str = "",
- timeout: int = 60000,
- ) -> bool:
+ def login_sync(self, *, login: int = 0, password: str = "", server: str = "", timeout: int = 60000) -> bool:
if self.config.use_terminal_for_backtesting:
- 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
- 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:
- return await super().login(
- login=login, password=password, server=server, timeout=timeout
- )
+ return await super().login(login=login, password=password, server=server, timeout=timeout)
return True
async def shutdown(self) -> None:
@@ -153,56 +99,28 @@ class MetaBackTester(MetaTrader):
return tick
@error_handler(msg="test data not available", exe=AttributeError)
- async def copy_rates_from(
- self, symbol: str, timeframe: TimeFrame, date_from: datetime | float, count: int
- ) -> ndarray | None:
- rates = await self.backtest_engine.get_rates_from(
- symbol=symbol, timeframe=timeframe, date_from=date_from, count=count
- )
+ async def copy_rates_from(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float, count: int) -> ndarray | None:
+ rates = await self.backtest_engine.get_rates_from(symbol=symbol, timeframe=timeframe, date_from=date_from, count=count)
return rates
@error_handler(msg="test data not available", exe=AttributeError)
- async def copy_rates_from_pos(
- self, symbol: str, timeframe: TimeFrame, start_pos: int, count: int
- ) -> ndarray | None:
- rates = await self.backtest_engine.get_rates_from_pos(
- symbol=symbol, timeframe=timeframe, start_pos=start_pos, count=count
- )
+ async def copy_rates_from_pos(self, symbol: str, timeframe: TimeFrame, start_pos: int, count: int) -> ndarray | None:
+ rates = await self.backtest_engine.get_rates_from_pos(symbol=symbol, timeframe=timeframe, start_pos=start_pos, count=count)
return rates
@error_handler(msg="test data not available", exe=AttributeError)
- async def copy_rates_range(
- self,
- symbol: str,
- timeframe: TimeFrame,
- date_from: datetime | float,
- date_to: datetime | float,
- ) -> ndarray | None:
- rates = await self.backtest_engine.get_rates_range(
- symbol=symbol, timeframe=timeframe, date_from=date_from, date_to=date_to
- )
+ async def copy_rates_range(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float, date_to: datetime | float) -> ndarray | None:
+ rates = await self.backtest_engine.get_rates_range(symbol=symbol, timeframe=timeframe, date_from=date_from, date_to=date_to)
return rates
@error_handler(msg="test data not available", exe=AttributeError)
- async def copy_ticks_from(
- self, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks
- ) -> ndarray | None:
- ticks = await self.backtest_engine.get_ticks_from(
- symbol=symbol, date_from=date_from, count=count, flags=flags
- )
+ async def copy_ticks_from(self, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks) -> ndarray | None:
+ ticks = await self.backtest_engine.get_ticks_from(symbol=symbol, date_from=date_from, count=count, flags=flags)
return ticks
@error_handler(msg="test data not available", exe=AttributeError)
- async def copy_ticks_range(
- self,
- symbol: str,
- date_from: datetime | float,
- date_to: datetime | float,
- flags: CopyTicks,
- ) -> ndarray | None:
- ticks = await self.backtest_engine.get_ticks_range(
- symbol=symbol, date_from=date_from, date_to=date_to, flags=flags
- )
+ async def copy_ticks_range(self, symbol: str, date_from: datetime | float, date_to: datetime | float, flags: CopyTicks) -> ndarray | None:
+ ticks = await self.backtest_engine.get_ticks_range(symbol=symbol, date_from=date_from, date_to=date_to, flags=flags)
return ticks
@error_handler(msg="test data not available", exe=AttributeError)
@@ -210,40 +128,19 @@ class MetaBackTester(MetaTrader):
return self.backtest_engine.get_orders_total()
@error_handler(msg="test data not available", exe=AttributeError)
- async def orders_get(
- self, group: str = "", ticket: int = 0, symbol: str = ""
- ) -> tuple[TradeOrder, ...] | None:
- kwargs = {
- key: value
- for key, value in (("group", group), ("ticket", ticket), ("symbol", symbol))
- if value
- }
+ async def orders_get(self, group: str = "", ticket: int = 0, symbol: str = "") -> tuple[TradeOrder, ...] | None:
+ kwargs = {key: value for key, value in (("group", group), ("ticket", ticket), ("symbol", symbol)) if value}
return self.backtest_engine.get_orders(**kwargs)
@error_handler(msg="test data not available", exe=AttributeError)
- async def order_calc_margin(
- self, action: OrderType, symbol: str, volume: float, price: float
- ) -> float | None:
- res = await self.backtest_engine.order_calc_margin(
- action=action, symbol=symbol, volume=volume, price=price
- )
+ async def order_calc_margin(self, action: OrderType, symbol: str, volume: float, price: float) -> float | None:
+ res = await self.backtest_engine.order_calc_margin(action=action, symbol=symbol, volume=volume, price=price)
return res
@error_handler(msg="test data not available", exe=AttributeError)
- async def order_calc_profit(
- self,
- action: Literal[0, 1],
- symbol: str,
- volume: float,
- price_open: float,
- price_close: float,
- ) -> float | None:
+ async def order_calc_profit(self, action: Literal[0, 1], symbol: str, volume: float, price_open: float, price_close: float) -> float | None:
profit = await self.backtest_engine.order_calc_profit(
- action=action,
- symbol=symbol,
- volume=volume,
- price_open=price_open,
- price_close=price_close,
+ action=action, symbol=symbol, volume=volume, price_open=price_open, price_close=price_close
)
return profit
@@ -261,66 +158,30 @@ class MetaBackTester(MetaTrader):
return self.backtest_engine.get_positions_total()
@error_handler(msg="test data not available", exe=AttributeError)
- async def positions_get(
- self, group: str = "", ticket: int = None, symbol: str = ""
- ) -> tuple[TradePosition, ...] | None:
- kwargs = {
- key: value
- for key, value in (("group", group), ("ticket", ticket), ("symbol", symbol))
- if value
- }
+ async def positions_get(self, group: str = "", ticket: int = None, symbol: str = "") -> tuple[TradePosition, ...] | None:
+ kwargs = {key: value for key, value in (("group", group), ("ticket", ticket), ("symbol", symbol)) if value}
return self.backtest_engine.get_positions(**kwargs)
@error_handler(msg="test data not available", exe=AttributeError)
- async def history_orders_total(
- self, date_from: datetime | float, date_to: datetime | float
- ) -> int | None:
- return self.backtest_engine.get_history_orders_total(
- date_from=date_from, date_to=date_to
- )
+ async def history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int | None:
+ return self.backtest_engine.get_history_orders_total(date_from=date_from, date_to=date_to)
@error_handler(msg="test data not available", exe=AttributeError)
async def history_orders_get(
- self,
- date_from: datetime | float = None,
- date_to: datetime | float = None,
- group: str = "",
- ticket: int = None,
- position: int = None,
+ self, date_from: datetime | float = None, date_to: datetime | float = None, group: str = "", ticket: int = None, position: int = None
) -> tuple[TradeOrder, ...] | None:
- args = (
- ("date_from", date_from),
- ("date_to", date_to),
- ("group", group),
- ("ticket", ticket),
- ("position", position),
- )
+ args = (("date_from", date_from), ("date_to", date_to), ("group", group), ("ticket", ticket), ("position", position))
kwargs = {key: value for key, value in args if value}
return self.backtest_engine.get_history_orders(**kwargs)
@error_handler(msg="test data not available", exe=AttributeError)
- async def history_deals_total(
- self, date_from: datetime | float, date_to: datetime | float
- ) -> int | None:
- return self.backtest_engine.get_history_deals_total(
- date_from=date_from, date_to=date_to
- )
+ async def history_deals_total(self, date_from: datetime | float, date_to: datetime | float) -> int | None:
+ return self.backtest_engine.get_history_deals_total(date_from=date_from, date_to=date_to)
@error_handler(msg="test data not available", exe=AttributeError)
async def history_deals_get(
- self,
- date_from: datetime | float = None,
- date_to: datetime | float = None,
- group: str = "",
- ticket: int = None,
- position: int = None,
+ self, date_from: datetime | float = None, date_to: datetime | float = None, group: str = "", ticket: int = None, position: int = None
) -> tuple[TradeDeal, ...] | None:
- args = (
- ("date_from", date_from),
- ("date_to", date_to),
- ("group", group),
- ("ticket", ticket),
- ("position", position),
- )
+ args = (("date_from", date_from), ("date_to", date_to), ("group", group), ("ticket", ticket), ("position", position))
kwargs = {key: value for key, value in args if value}
return self.backtest_engine.get_history_deals(**kwargs)
diff --git a/src/aiomql/core/meta_trader.py b/src/aiomql/core/meta_trader.py
index d99b5d2..7ca328d 100644
--- a/src/aiomql/core/meta_trader.py
+++ b/src/aiomql/core/meta_trader.py
@@ -5,18 +5,8 @@ from typing import Literal
from pathlib import Path
import numpy as np
-from MetaTrader5 import (
- BookInfo,
- SymbolInfo,
- AccountInfo,
- Tick,
- TerminalInfo,
- TradeOrder,
- TradeDeal,
- TradePosition,
- OrderSendResult,
- OrderCheckResult,
-)
+from MetaTrader5 import (BookInfo, SymbolInfo, AccountInfo, Tick, TerminalInfo, TradeOrder, TradeDeal, TradePosition,
+ OrderSendResult, OrderCheckResult)
import MetaTrader5 as mt5
from .constants import OrderType, CopyTicks
@@ -75,14 +65,7 @@ class MetaTrader(MetaCore):
logger.warning(f"{error_msg}:{self.error.description}")
return res
- 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:
"""
Connects to the MetaTrader terminal using the specified login, password and server.
@@ -99,18 +82,9 @@ class MetaTrader(MetaCore):
login = login or acc_details.get("login", 0)
password = password or acc_details.get("password", "")
server = server or acc_details.get("server", "")
- return await asyncio.to_thread(
- self._login, login, password=password, server=server, timeout=timeout
- )
+ return await asyncio.to_thread(self._login, login, password=password, server=server, timeout=timeout)
- def login_sync(
- self,
- *,
- login: int = 0,
- password: str = "",
- server: str = "",
- timeout: int = 60000,
- ) -> bool:
+ 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.
@@ -131,13 +105,7 @@ class MetaTrader(MetaCore):
return res
async def initialize(
- self,
- path: str = None,
- login: int = 0,
- password: str = "",
- server: str = "",
- timeout: int | None = None,
- portable=False,
+ 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.
@@ -179,13 +147,7 @@ class MetaTrader(MetaCore):
return res
def initialize_sync(
- self,
- path: str = None,
- login: int = 0,
- password: str = "",
- server: str = "",
- timeout: int | None = None,
- portable=False,
+ 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.
@@ -244,107 +206,62 @@ class MetaTrader(MetaCore):
async def account_info(self) -> AccountInfo | None:
""""""
- api = {
- "func": self._account_info,
- "error_msg": "Error in obtaining account information",
- }
+ api = {"func": self._account_info, "error_msg": "Error in obtaining account information"}
res = await self._handler(api)
return res
async def terminal_info(self) -> TerminalInfo | None:
- api = {
- "func": self._terminal_info,
- "error_msg": "Error in obtaining terminal information",
- }
+ api = {"func": self._terminal_info, "error_msg": "Error in obtaining terminal information"}
res = await self._handler(api)
return res
async def symbols_total(self) -> int:
- api = {
- "func": self._symbols_total,
- "error_msg": "Error in obtaining total symbols.",
- }
+ api = {"func": self._symbols_total, "error_msg": "Error in obtaining total symbols."}
res = await self._handler(api)
return res
async def symbols_get(self, group: str = "") -> tuple[SymbolInfo] | None:
kwargs = {"group": group} if group else {}
- api = {
- "func": self._symbols_get,
- "kwargs": kwargs,
- "error_msg": "Error in obtaining symbols.",
- }
+ api = {"func": self._symbols_get, "kwargs": kwargs, "error_msg": "Error in obtaining symbols."}
res = await self._handler(api)
return res
async def symbol_info(self, symbol: str) -> SymbolInfo | None:
- api = {
- "func": self._symbol_info,
- "args": (symbol,),
- "error_msg": f"Error in obtaining information for {symbol}",
- }
+ api = {"func": self._symbol_info, "args": (symbol,), "error_msg": f"Error in obtaining information for {symbol}"}
res = await self._handler(api)
return res
async def symbol_info_tick(self, symbol: str) -> Tick | None:
- api = {
- "func": self._symbol_info_tick,
- "args": (symbol,),
- "error_msg": f"Error in obtaining tick for {symbol}",
- }
+ api = {"func": self._symbol_info_tick, "args": (symbol,), "error_msg": f"Error in obtaining tick for {symbol}"}
res = await self._handler(api)
return res
async def symbol_select(self, symbol: str, enable: bool) -> bool:
- api = {
- "func": self._symbol_select,
- "args": (symbol, enable),
- "error_msg": f"Error in selecting {symbol}",
- }
+ api = {"func": self._symbol_select, "args": (symbol, enable), "error_msg": f"Error in selecting {symbol}"}
res = await self._handler(api)
return res
async def market_book_add(self, symbol: str) -> bool:
- api = {
- "func": self._market_book_add,
- "args": (symbol,),
- "error_msg": f"Error in adding {symbol} to market book",
- }
+ api = {"func": self._market_book_add, "args": (symbol,), "error_msg": f"Error in adding {symbol} to market book"}
res = await self._handler(api)
return res
async def market_book_get(self, symbol: str) -> tuple[BookInfo] | None:
- api = {
- "func": self._market_book_get,
- "args": (symbol,),
- "error_msg": f"Error in obtaining market depth for {symbol}",
- }
+ api = {"func": self._market_book_get, "args": (symbol,), "error_msg": f"Error in obtaining market depth for {symbol}"}
res = await self._handler(api)
return res
async def market_book_release(self, symbol: str) -> bool:
- api = {
- "func": self._market_book_release,
- "args": (symbol,),
- "error_msg": f"Error in releasing market depth for {symbol}",
- }
+ api = {"func": self._market_book_release, "args": (symbol,), "error_msg": f"Error in releasing market depth for {symbol}"}
res = await self._handler(api)
return res
- async def copy_rates_from(
- self, symbol: str, timeframe: int, date_from: datetime | float, count: int
- ) -> np.ndarray | None:
- api = {
- "func": self._copy_rates_from,
- "args": (symbol, timeframe, date_from, count),
- "error_msg": f"Error in obtaining rates for {symbol}",
- }
+ async def copy_rates_from(self, symbol: str, timeframe: int, date_from: datetime | float, count: int) -> np.ndarray | None:
+ api = {"func": self._copy_rates_from, "args": (symbol, timeframe, date_from, count), "error_msg": f"Error in obtaining rates for {symbol}"}
res = await self._handler(api)
return res
- async def copy_rates_from_pos(
- self, symbol: str, timeframe: int, start_pos: int, count: int
- ) -> np.ndarray | None:
+ async def copy_rates_from_pos(self, symbol: str, timeframe: int, start_pos: int, count: int) -> np.ndarray | None:
api = {
"func": self._copy_rates_from_pos,
"args": (symbol, timeframe, start_pos, count),
@@ -353,93 +270,39 @@ class MetaTrader(MetaCore):
res = await self._handler(api)
return res
- async def copy_rates_range(
- self,
- symbol: str,
- timeframe: int,
- date_from: datetime | float,
- date_to: datetime | float,
- ) -> np.ndarray | None:
- api = {
- "func": self._copy_rates_range,
- "args": (symbol, timeframe, date_from, date_to),
- "error_msg": f"Error in obtaining rates for {symbol}",
- }
+ async def copy_rates_range(self, symbol: str, timeframe: int, date_from: datetime | float, date_to: datetime | float) -> np.ndarray | None:
+ api = {"func": self._copy_rates_range, "args": (symbol, timeframe, date_from, date_to), "error_msg": f"Error in obtaining rates for {symbol}"}
res = await self._handler(api)
return res
- async def copy_ticks_from(
- self, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks
- ) -> np.ndarray | None:
- api = {
- "func": self._copy_ticks_from,
- "args": (symbol, date_from, count, flags),
- "error_msg": f"Error in obtaining ticks for {symbol}",
- }
+ async def copy_ticks_from(self, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks) -> np.ndarray | None:
+ api = {"func": self._copy_ticks_from, "args": (symbol, date_from, count, flags), "error_msg": f"Error in obtaining ticks for {symbol}"}
res = await self._handler(api)
return res
- async def copy_ticks_range(
- self,
- symbol: str,
- date_from: datetime | float,
- date_to: datetime | float,
- flags: CopyTicks,
- ) -> np.ndarray | None:
- api = {
- "func": self._copy_ticks_range,
- "args": (symbol, date_from, date_to, flags),
- "error_msg": f"Error in obtaining ticks for {symbol}",
- }
+ async def copy_ticks_range(self, symbol: str, date_from: datetime | float, date_to: datetime | float, flags: CopyTicks) -> np.ndarray | None:
+ api = {"func": self._copy_ticks_range, "args": (symbol, date_from, date_to, flags), "error_msg": f"Error in obtaining ticks for {symbol}"}
res = await self._handler(api)
return res
async def orders_total(self) -> int:
- api = {
- "func": self._orders_total,
- "error_msg": "Error in obtaining total orders.",
- }
+ api = {"func": self._orders_total, "error_msg": "Error in obtaining total orders."}
res = await self._handler(api)
return res
- async def orders_get(
- self, group: str = "", ticket: int = 0, symbol: str = ""
- ) -> tuple[TradeOrder] | None:
- kwargs = {
- key: value
- for key, value in (("group", group), ("ticket", ticket), ("symbol", symbol))
- if value
- }
- api = {
- "func": self._orders_get,
- "kwargs": kwargs,
- "error_msg": "Error in obtaining orders.",
- }
+ async def orders_get(self, group: str = "", ticket: int = 0, symbol: str = "") -> tuple[TradeOrder] | None:
+ kwargs = {key: value for key, value in (("group", group), ("ticket", ticket), ("symbol", symbol)) if value}
+ api = {"func": self._orders_get, "kwargs": kwargs, "error_msg": "Error in obtaining orders."}
res = await self._handler(api)
return res
- async def order_calc_margin(
- self,
- action: Literal[OrderType.BUY, OrderType.SELL],
- symbol: str,
- volume: float,
- price: float,
- ) -> float | None:
- api = {
- "func": self._order_calc_margin,
- "args": (action, symbol, volume, price),
- "error_msg": "Error in calculating margin.",
- }
+ async def order_calc_margin(self, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float, price: float) -> float | None:
+ api = {"func": self._order_calc_margin, "args": (action, symbol, volume, price), "error_msg": "Error in calculating margin."}
res = await self._handler(api)
return res
async def order_calc_profit(
- self,
- action: Literal[OrderType.BUY, OrderType.SELL],
- symbol: str,
- volume: float,
- price_open: float,
- price_close: float,
+ self, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float, price_open: float, price_close: float
) -> float | None:
api = {
"func": self._order_calc_profit,
@@ -450,119 +313,50 @@ class MetaTrader(MetaCore):
return res
async def order_check(self, request: dict) -> OrderCheckResult:
- api = {
- "func": self._order_check,
- "args": (request,),
- "error_msg": "Error in checking order.",
- }
+ api = {"func": self._order_check, "args": (request,), "error_msg": "Error in checking order."}
res = await self._handler(api)
return res
async def order_send(self, request: dict) -> OrderSendResult:
- api = {
- "func": self._order_send,
- "args": (request,),
- "error_msg": "Error in sending order.",
- }
+ api = {"func": self._order_send, "args": (request,), "error_msg": "Error in sending order."}
res = await self._handler(api)
return res
async def positions_total(self) -> int:
- api = {
- "func": self._positions_total,
- "error_msg": "Error in obtaining total positions.",
- }
+ api = {"func": self._positions_total, "error_msg": "Error in obtaining total positions."}
res = await self._handler(api)
return res
- async def positions_get(
- self, group: str = "", ticket: int = None, symbol: str = ""
- ) -> tuple[TradePosition] | None:
- kwargs = {
- key: value
- for key, value in (("group", group), ("ticket", ticket), ("symbol", symbol))
- if value
- }
- api = {
- "func": self._positions_get,
- "kwargs": kwargs,
- "error_msg": "Error in obtaining open positions.",
- }
+ async def positions_get(self, group: str = "", ticket: int = None, symbol: str = "") -> tuple[TradePosition] | None:
+ kwargs = {key: value for key, value in (("group", group), ("ticket", ticket), ("symbol", symbol)) if value}
+ api = {"func": self._positions_get, "kwargs": kwargs, "error_msg": "Error in obtaining open positions."}
res = await self._handler(api)
return res
- async def history_orders_total(
- self, date_from: datetime | float, date_to: datetime | float
- ) -> int:
- api = {
- "func": self._history_orders_total,
- "args": (date_from, date_to),
- "error_msg": "Error in obtaining total history orders.",
- }
+ async def history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
+ api = {"func": self._history_orders_total, "args": (date_from, date_to), "error_msg": "Error in obtaining total history orders."}
res = await self._handler(api)
return res
async def history_orders_get(
- self,
- date_from: datetime | float = None,
- date_to: datetime | float = None,
- group: str = "",
- ticket: int = None,
- position: int = None,
+ self, date_from: datetime | float = None, date_to: datetime | float = None, group: str = "", ticket: int = None, position: int = None
) -> tuple[TradeOrder] | None:
- kwargs = {
- key: value
- for key, value in (
- ("group", group),
- ("ticket", ticket),
- ("position", position),
- )
- if value
- }
+ kwargs = {key: value for key, value in (("group", group), ("ticket", ticket), ("position", position)) if value}
args = tuple(arg for arg in (date_from, date_to) if arg)
- api = {
- "func": self._history_orders_get,
- "args": args,
- "kwargs": kwargs,
- "error_msg": "Error in obtaining history orders",
- }
+ api = {"func": self._history_orders_get, "args": args, "kwargs": kwargs, "error_msg": "Error in obtaining history orders"}
res = await self._handler(api)
return res
- async def history_deals_total(
- self, date_from: datetime | float, date_to: datetime | float
- ) -> int:
- api = {
- "func": self._history_deals_total,
- "args": (date_from, date_to),
- "error_msg": "Error in obtaining total history deals",
- }
+ async def history_deals_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
+ api = {"func": self._history_deals_total, "args": (date_from, date_to), "error_msg": "Error in obtaining total history deals"}
res = await self._handler(api)
return res
async def history_deals_get(
- self,
- date_from: datetime | float = None,
- date_to: datetime | float = None,
- group: str = "",
- ticket: int = None,
- position: int = None,
+ self, date_from: datetime | float = None, date_to: datetime | float = None, group: str = "", ticket: int = None, position: int = None
) -> tuple[TradeDeal] | None:
- kwargs = {
- key: value
- for key, value in (
- ("group", group),
- ("ticket", ticket),
- ("position", position),
- )
- if value
- }
+ kwargs = {key: value for key, value in (("group", group), ("ticket", ticket), ("position", position)) if value}
args = tuple(arg for arg in (date_from, date_to) if arg)
- api = {
- "func": self._history_deals_get,
- "args": args,
- "kwargs": kwargs,
- "error_msg": "Error in obtaining history deals",
- }
+ api = {"func": self._history_deals_get, "args": args, "kwargs": kwargs, "error_msg": "Error in obtaining history deals"}
res = await self._handler(api)
return res
diff --git a/src/aiomql/core/models.py b/src/aiomql/core/models.py
index 7ba1a76..0538456 100644
--- a/src/aiomql/core/models.py
+++ b/src/aiomql/core/models.py
@@ -354,10 +354,7 @@ class SymbolInfo(Base):
name: str = ""
def __repr__(self):
- return "%(class)s(name=%(name)s)" % {
- "class": self.__class__.__name__,
- "name": self.name,
- }
+ return "%(class)s(name=%(name)s)" % {"class": self.__class__.__name__, "name": self.name}
def __str__(self):
return self.name
diff --git a/src/aiomql/core/task_queue.py b/src/aiomql/core/task_queue.py
index 52c707b..75cd0a2 100644
--- a/src/aiomql/core/task_queue.py
+++ b/src/aiomql/core/task_queue.py
@@ -31,9 +31,7 @@ class QueueItem:
self.task_item(*self.args, **self.kwargs)
except Exception as err:
- logger.error(
- f"Error {err} occurred in {self.task_item.__name__} with args {self.args} and kwargs {self.kwargs}"
- )
+ logger.error(f"Error {err} occurred in {self.task_item.__name__} with args {self.args} and kwargs {self.kwargs}")
class TaskQueue:
@@ -84,9 +82,7 @@ class TaskQueue:
self.queue.task_done()
self.priority_tasks.discard(item)
- if self.stop and (
- self.on_exit == "cancel" or len(self.priority_tasks) == 0
- ):
+ if self.stop and (self.on_exit == "cancel" or len(self.priority_tasks) == 0):
self.cancel()
break
@@ -107,36 +103,26 @@ class TaskQueue:
async def run(self, timeout: int = 0):
start = time.perf_counter()
try:
- self.tasks.extend(
- asyncio.create_task(self.worker()) for _ in range(self.workers)
- )
+ self.tasks.extend(asyncio.create_task(self.worker()) for _ in range(self.workers))
timeout = timeout or self.timeout
queue_task = asyncio.create_task(self.queue.join())
if timeout:
- main_task = asyncio.create_task(
- asyncio.wait_for(queue_task, timeout=timeout)
- )
+ main_task = asyncio.create_task(asyncio.wait_for(queue_task, timeout=timeout))
else:
main_task = queue_task
self.tasks.append(main_task)
await main_task
except TimeoutError:
- logger.warning(
- "Timed out after %d seconds, %d tasks remaining",
- time.perf_counter() - start,
- self.queue.qsize(),
- )
+ logger.warning("Timed out after %d seconds, %d tasks remaining", time.perf_counter() - start, self.queue.qsize())
self.stop = True
except asyncio.CancelledError as _:
logger.warning("Main task cancelled")
except Exception as err:
- logger.warning(
- "%s: An error occurred in %s.run", err, self.__class__.__name__
- )
+ logger.warning("%s: An error occurred in %s.run", err, self.__class__.__name__)
finally:
await self.clean_up()
@@ -149,9 +135,7 @@ class TaskQueue:
async def clean_up(self):
try:
if self.on_exit == "complete_priority" and len(self.priority_tasks) > 0:
- logger.warning(
- f"Completing {len(self.priority_tasks)} priority tasks..."
- )
+ logger.warning(f"Completing {len(self.priority_tasks)} priority tasks...")
queue_task = asyncio.create_task(self.queue.join())
self.tasks.append(queue_task)
await queue_task
@@ -161,9 +145,7 @@ class TaskQueue:
...
except Exception as err:
- logger.error(
- f"%s: Error occurred in %s.clean_up", err, self.__class__.__name__
- )
+ logger.error(f"%s: Error occurred in %s.clean_up", err, self.__class__.__name__)
finally:
self.cancel()
diff --git a/src/aiomql/lib/account.py b/src/aiomql/lib/account.py
index 41897ce..d0a6f7e 100644
--- a/src/aiomql/lib/account.py
+++ b/src/aiomql/lib/account.py
@@ -15,6 +15,7 @@ class Account(_Base, AccountInfo):
Attributes:
connected (bool): Status of connection to MetaTrader 5 Terminal
"""
+
_instance: Self
connected: bool
diff --git a/src/aiomql/lib/backtester.py b/src/aiomql/lib/backtester.py
index 75c2d74..68e330d 100644
--- a/src/aiomql/lib/backtester.py
+++ b/src/aiomql/lib/backtester.py
@@ -25,6 +25,7 @@ class BackTester:
config (Config): Config instance
mt (MetaBackTester): MetaTrader instance
"""
+
config: Config
executor: Executor
mt: MetaBackTester
@@ -57,18 +58,12 @@ class BackTester:
self.backtest_engine.setup_account_sync()
self.init_strategies_sync()
if (strategies := len(self.executor.strategy_runners)) == 0:
- logger.warning(
- "No strategies were added to the backtester. Exiting ..."
- )
+ logger.warning("No strategies were added to the backtester. Exiting ...")
raise Exception("No strategies added to the backtester")
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.backtest_controller.control, on_separate_thread=True
- )
+ self.add_coroutine(coroutine=self.backtest_controller.control, on_separate_thread=True)
parties = strategies + 1
self.backtest_controller.set_parties(parties=parties)
except Exception as err:
@@ -92,31 +87,19 @@ class BackTester:
await self.backtest_engine.setup_account()
await self.init_strategies()
if (strategies := len(self.executor.strategy_runners)) == 0:
- logger.warning(
- "No strategies were added to the backtester. Exiting ..."
- )
+ logger.warning("No strategies were added to the backtester. Exiting ...")
raise Exception("No strategies added to the backtester")
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.backtest_controller.control, on_separate_thread=True
- )
+ self.add_coroutine(coroutine=self.backtest_controller.control, on_separate_thread=True)
parties = strategies + 1
self.backtest_controller.set_parties(parties=parties)
except Exception as err:
logger.error(f"{err}. Backtester initialization failed")
raise SystemExit
- def add_coroutine(
- self,
- *,
- coroutine: Callable[..., ...] | Coroutine,
- on_separate_thread=False,
- **kwargs,
- ):
+ def add_coroutine(self, *, coroutine: Callable[..., ...] | Coroutine, on_separate_thread=False, **kwargs):
"""Add a coroutine to the executor.
Args:
@@ -127,9 +110,7 @@ class BackTester:
Returns:
"""
- 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):
"""Execute the bot."""
@@ -161,14 +142,7 @@ class BackTester:
"""
[self.add_strategy(strategy=strategy) for strategy in strategies]
- def add_strategy_all(
- self,
- *,
- strategy: Type[Strategy],
- params: dict | None = None,
- symbols: list[Symbol] = None,
- **kwargs,
- ):
+ def add_strategy_all(self, *, 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.
Keyword Args:
@@ -177,10 +151,7 @@ class BackTester:
symbols (list): A list of symbols to run the strategy on
**kwargs: Additional keyword arguments for the strategy
"""
- [
- self.add_strategy(strategy=strategy(symbol=symbol, params=params, **kwargs))
- for symbol in symbols
- ]
+ [self.add_strategy(strategy=strategy(symbol=symbol, params=params, **kwargs)) for symbol in symbols]
async def init_strategy(self, *, strategy: Strategy) -> bool:
"""Initialize a single strategy. This method is called internally by the bot."""
diff --git a/src/aiomql/lib/bot.py b/src/aiomql/lib/bot.py
index c10e678..cf9dca2 100644
--- a/src/aiomql/lib/bot.py
+++ b/src/aiomql/lib/bot.py
@@ -21,6 +21,7 @@ class Bot:
config (Config): Config instance
mt (MetaTrader): MetaTrader instance
"""
+
config: Config
executor: Executor
mt: MetaTrader
@@ -61,9 +62,7 @@ class Bot:
raise Exception("Unable to sign in to MetaTrader 5 Terminal")
logger.info("Login Successful")
await self.init_strategies()
- 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)
if len(self.executor.strategy_runners) == 0:
@@ -87,9 +86,7 @@ class Bot:
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.config.task_queue.run, on_separate_thread=True)
self.add_coroutine(coroutine=self.executor.exit)
if len(self.executor.strategy_runners) == 0:
@@ -107,13 +104,7 @@ class Bot:
"""
self.executor.add_function(function=function, kwargs=kwargs)
- def add_coroutine(
- self,
- *,
- coroutine: Callable[..., ...] | Coroutine,
- on_separate_thread=False,
- **kwargs,
- ):
+ def add_coroutine(self, *, coroutine: Callable[..., ...] | Coroutine, on_separate_thread=False, **kwargs):
"""Add a coroutine to the executor.
Args:
@@ -124,9 +115,7 @@ class Bot:
Returns:
"""
- 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):
"""Execute the bot using asyncio.run"""
@@ -157,14 +146,7 @@ class Bot:
"""
[self.add_strategy(strategy=strategy) for strategy in strategies]
- def add_strategy_all(
- self,
- *,
- strategy: Type[Strategy],
- params: dict | None = None,
- symbols: list[Symbol] = None,
- **kwargs,
- ):
+ def add_strategy_all(self, *, 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.
Keyword Args:
@@ -173,10 +155,7 @@ class Bot:
symbols (list): A list of symbols to run the strategy on
**kwargs: Additional keyword arguments for the strategy
"""
- [
- self.add_strategy(strategy=strategy(symbol=symbol, params=params, **kwargs))
- for symbol in symbols
- ]
+ [self.add_strategy(strategy=strategy(symbol=symbol, params=params, **kwargs)) for symbol in symbols]
async def init_strategy(self, *, strategy: Strategy) -> bool:
"""Initialize a single strategy. This method is called internally by the bot."""
@@ -200,7 +179,7 @@ class Bot:
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
+ info["select"] = select
tick = Tick(**tick._asdict())
strategy.symbol.tick = tick
strategy.symbol.set_attributes(**info)
diff --git a/src/aiomql/lib/candle.py b/src/aiomql/lib/candle.py
index 87ea65d..623e242 100644
--- a/src/aiomql/lib/candle.py
+++ b/src/aiomql/lib/candle.py
@@ -1,4 +1,5 @@
"""Candle and Candles classes for handling bars from the MetaTrader 5 terminal."""
+
import time
from typing import Type, Self, Iterable
from logging import getLogger
@@ -45,9 +46,7 @@ class Candle:
**kwargs: Candle attributes and values as keyword arguments.
"""
if not all(i in kwargs for i in ["open", "high", "low", "close"]):
- raise ValueError(
- "Candle must be instantiated with open, high, low and close prices"
- )
+ raise ValueError("Candle must be instantiated with open, high, low and close prices")
self.time = kwargs.pop("time", time.monotonic_ns())
self.Index = kwargs.pop("Index", 0)
self.real_volume = kwargs.pop("real_volume", 0)
@@ -56,18 +55,15 @@ class Candle:
self.set_attributes(**kwargs)
def __repr__(self):
- return (
- "%(class)s(Index=%(Index)s, time=%(time)s, open=%(open)s, high=%(high)s, low=%(low)s, close=%(close)s)"
- % {
- "class": self.__class__.__name__,
- "open": self.open,
- "high": self.high,
- "low": self.low,
- "close": self.close,
- "time": self.time,
- "Index": self.Index,
- }
- )
+ return "%(class)s(Index=%(Index)s, time=%(time)s, open=%(open)s, high=%(high)s, low=%(low)s, close=%(close)s)" % {
+ "class": self.__class__.__name__,
+ "open": self.open,
+ "high": self.high,
+ "low": self.low,
+ "close": self.close,
+ "time": self.time,
+ "Index": self.Index,
+ }
def __eq__(self, other: Self):
return self.time == other.time
@@ -170,13 +166,7 @@ class Candles:
timeframe: TimeFrame
_data: DataFrame
- def __init__(
- self,
- *,
- data: DataFrame | Self | Iterable,
- flip=False,
- candle_class: Candle = None,
- ):
+ def __init__(self, *, data: DataFrame | Self | Iterable, flip=False, candle_class: Candle = None):
"""A container class of Candle objects in chronological order.
Args:
@@ -236,9 +226,7 @@ class Candles:
if item == "Index":
return Series(self._data.index)
- raise AttributeError(
- f"Attribute {item} not defined on class {self.__class__.__name__}"
- )
+ raise AttributeError(f"Attribute {item} not defined on class {self.__class__.__name__}")
def __iter__(self):
return (self.Candle(**row._asdict()) for row in self._data.itertuples())
diff --git a/src/aiomql/lib/executor.py b/src/aiomql/lib/executor.py
index 7087215..30cbac2 100644
--- a/src/aiomql/lib/executor.py
+++ b/src/aiomql/lib/executor.py
@@ -19,6 +19,7 @@ class Executor:
coroutines (list[Coroutine]): A list of coroutines to run in the executor
functions (dict[Callable, dict]): A dictionary of functions to run in the executor
"""
+
executor: ThreadPoolExecutor
tasks: list[asyncio.Task]
config: Config
@@ -36,18 +37,10 @@ class Executor:
kwargs = kwargs or {}
self.functions[function] = kwargs
- 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 {}
coroutine = coroutine(**kwargs)
- self.coroutines.append(
- coroutine
- ) if on_separate_thread is False else self.coroutine_threads.append(coroutine)
+ self.coroutines.append(coroutine) if on_separate_thread is False else self.coroutine_threads.append(coroutine)
def add_strategies(self, *, strategies: tuple[Strategy]):
"""Add multiple strategies at once
@@ -85,9 +78,7 @@ class Executor:
async def create_coroutines_task(self):
""""""
- task = asyncio.create_task(
- asyncio.gather(*self.coroutines, return_exceptions=True)
- )
+ task = asyncio.create_task(asyncio.gather(*self.coroutines, return_exceptions=True))
self.tasks.append(task)
await task
@@ -116,9 +107,7 @@ class Executor:
start = asyncio.get_event_loop().time()
try:
while self.config.shutdown is False and self.config.force_shutdown is False:
- 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
for strategy in self.strategy_runners:
@@ -140,25 +129,11 @@ class Executor:
Notes:
No matter the number specified, the executor will always use a minimum of 5 workers.
"""
- workers_ = (
- len(self.strategy_runners)
- + len(self.functions)
- + len(self.coroutine_threads)
- + 2
- )
+ workers_ = len(self.strategy_runners) + len(self.functions) + len(self.coroutine_threads) + 2
workers = max(workers, workers_)
with ThreadPoolExecutor(max_workers=workers) as executor:
self.executor = executor
- [
- 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(self.run_coroutine_task, coroutine)
- for coroutine in self.coroutine_threads
- ]
+ [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(self.run_coroutine_task, coroutine) for coroutine in self.coroutine_threads]
self.executor.submit(self.run_coroutine_tasks)
diff --git a/src/aiomql/lib/history.py b/src/aiomql/lib/history.py
index 4e1a024..240b128 100644
--- a/src/aiomql/lib/history.py
+++ b/src/aiomql/lib/history.py
@@ -34,14 +34,7 @@ class History:
total_orders: int
group: str
- def __init__(
- self,
- *,
- date_from: datetime | float,
- date_to: datetime | float,
- group: str = "",
- use_utc: bool = True,
- ):
+ def __init__(self, *, date_from: datetime | float, date_to: datetime | float, group: str = "", use_utc: bool = True):
"""
Args:
date_from (datetime, float): Date the orders are requested from. Set by the 'datetime' object or as a
@@ -54,16 +47,8 @@ class History:
"""
self.config = Config()
self.mt5 = MetaTrader() if self.config.mode != "backtest" else MetaBackTester()
- date_from = (
- date_from
- if isinstance(date_from, datetime)
- else datetime.fromtimestamp(date_from)
- )
- date_to = (
- date_to
- if isinstance(date_to, datetime)
- else datetime.fromtimestamp(date_to)
- )
+ date_from = date_from if isinstance(date_from, datetime) else datetime.fromtimestamp(date_from)
+ date_to = date_to if isinstance(date_to, datetime) else datetime.fromtimestamp(date_to)
self.date_from = date_from.astimezone(pytz.UTC) if use_utc else date_from
self.date_to = date_to.astimezone(pytz.UTC) if use_utc else date_to
self.group = group
@@ -74,9 +59,7 @@ class History:
async def initialize(self):
"""Get history deals and orders"""
- deals, orders = await asyncio.gather(
- self.get_deals(), self.get_orders(), return_exceptions=True
-)
+ deals, orders = await asyncio.gather(self.get_deals(), self.get_orders(), return_exceptions=True)
self.deals = deals if isinstance(deals, tuple) else ()
self.orders = orders if isinstance(orders, tuple) else ()
self.total_deals = len(self.deals)
@@ -89,9 +72,7 @@ class History:
Returns:
tuple[TradeDeal, ...]: A list of trade deals
"""
- deals = await self.mt5.history_deals_get(
- date_from=self.date_from, date_to=self.date_to, group=self.group
- )
+ deals = await self.mt5.history_deals_get(date_from=self.date_from, date_to=self.date_to, group=self.group)
if deals is not None:
return tuple(TradeDeal(**deal._asdict()) for deal in deals)
logger.warning(f"Failed to get deals")
@@ -107,12 +88,7 @@ class History:
Returns:
tuple[TradeDeal]: A tuple of all deals with the order ticket
"""
- return tuple(
- sorted(
- (deal for deal in self.deals if deal.order == ticket),
- key=lambda x: x.time_msc,
- )
- )
+ return tuple(sorted((deal for deal in self.deals if deal.order == ticket), key=lambda x: x.time_msc))
def get_deals_by_position(self, *, position: int = None) -> tuple[TradeDeal, ...]:
"""
@@ -123,12 +99,7 @@ class History:
Returns:
tuple[TradeDeal]: A tuple of all deals with the position ticket
"""
- return tuple(
- sorted(
- (deal for deal in self.deals if deal.position_id == position),
- key=lambda x: x.time_msc,
- )
- )
+ return tuple(sorted((deal for deal in self.deals if deal.position_id == position), key=lambda x: x.time_msc))
@backoff_decorator
async def get_orders(self) -> tuple[TradeOrder, ...]:
@@ -137,9 +108,7 @@ class History:
Returns:
list[TradeOrder]: A list of trade orders
"""
- orders = await self.mt5.history_orders_get(
- date_from=self.date_from, date_to=self.date_to, group=self.group
- )
+ orders = await self.mt5.history_orders_get(date_from=self.date_from, date_to=self.date_to, group=self.group)
if orders is not None:
return tuple(TradeOrder(**order._asdict()) for order in orders)
@@ -149,18 +118,8 @@ class History:
def get_orders_by_ticket(self, *, ticket: int) -> tuple[TradeOrder, ...]:
"""filter orders by ticket"""
- return tuple(
- sorted(
- (order for order in self.orders if order.ticket == ticket),
- key=lambda x: x.time_done_msc,
- )
- )
+ return tuple(sorted((order for order in self.orders if order.ticket == ticket), key=lambda x: x.time_done_msc))
def get_orders_by_position(self, *, position: int) -> tuple[TradeOrder, ...]:
"""filter orders by position"""
- return tuple(
- sorted(
- (order for order in self.orders if order.position_id == position),
- key=lambda x: x.time_done_msc,
- )
- )
+ return tuple(sorted((order for order in self.orders if order.position_id == position), key=lambda x: x.time_done_msc))
diff --git a/src/aiomql/lib/order.py b/src/aiomql/lib/order.py
index b4af659..56c06f2 100644
--- a/src/aiomql/lib/order.py
+++ b/src/aiomql/lib/order.py
@@ -25,12 +25,7 @@ class Order(_Base, TradeRequest):
type_time (OrderTime.DAY): Order time
type_filling (OrderFilling.FOK): Order filling
"""
- kwargs = {
- "action": TradeAction.DEAL,
- "type_time": OrderTime.DAY,
- "type_filling": OrderFilling.FOK,
- **kwargs,
- }
+ kwargs = {"action": TradeAction.DEAL, "type_time": OrderTime.DAY, "type_filling": OrderFilling.FOK, **kwargs}
super().__init__(**kwargs)
async def orders_total(self):
@@ -111,9 +106,7 @@ class Order(_Base, TradeRequest):
Returns:
float: Returns float value if successful
"""
- res = await self.mt5.order_calc_margin(
- self.type, self.symbol, self.volume, self.price
- )
+ res = await self.mt5.order_calc_margin(self.type, self.symbol, self.volume, self.price)
return res
@error_handler(response=0, log_error_msg=False)
@@ -124,16 +117,8 @@ class Order(_Base, TradeRequest):
float: Returns float value if successful
None: If not successful
"""
- action, symbol, volume, price_open, price_close = (
- self.type,
- self.symbol,
- self.volume,
- self.price,
- self.tp,
- )
- res = await self.mt5.order_calc_profit(
- action, symbol, volume, price_open, price_close
- )
+ action, symbol, volume, price_open, price_close = (self.type, self.symbol, self.volume, self.price, self.tp)
+ res = await self.mt5.order_calc_profit(action, symbol, volume, price_open, price_close)
return res
@error_handler(response=0, log_error_msg=False)
@@ -144,23 +129,11 @@ class Order(_Base, TradeRequest):
float: Returns float value if successful
None: If not successful
"""
- action, symbol, volume, price_open, price_close = (
- self.type,
- self.symbol,
- self.volume,
- self.price,
- self.sl,
- )
- res = await self.mt5.order_calc_profit(
- action, symbol, volume, price_open, price_close
- )
+ action, symbol, volume, price_open, price_close = (self.type, self.symbol, self.volume, self.price, self.sl)
+ res = await self.mt5.order_calc_profit(action, symbol, volume, price_open, price_close)
return res
@property
def request(self) -> dict:
"""Return the order request as a dictionary."""
- return {
- key: value
- for key, value in self.dict.items()
- if key in self.mt5.TradeRequest.__match_args__
- }
+ return {key: value for key, value in self.dict.items() if key in self.mt5.TradeRequest.__match_args__}
diff --git a/src/aiomql/lib/positions.py b/src/aiomql/lib/positions.py
index c30c699..30709af 100644
--- a/src/aiomql/lib/positions.py
+++ b/src/aiomql/lib/positions.py
@@ -1,4 +1,5 @@
"""Handle Open positions."""
+
import asyncio
from logging import getLogger
@@ -70,9 +71,7 @@ class Positions:
return tuple(TradePosition(**pos._asdict()) for pos in (positions or ()))
@staticmethod
- async def close(
- *, ticket: int, symbol: str, price: float, volume: float, order_type: OrderType
- ) -> OrderSendResult:
+ async def close(*, ticket: int, symbol: str, price: float, volume: float, order_type: OrderType) -> OrderSendResult:
"""Close an open position for the trading account using the ticket and other parameters.
Args:
@@ -82,14 +81,7 @@ class Positions:
volume (float): Volume to close.
order_type (OrderType): Order type.
"""
- order = Order(
- action=TradeAction.DEAL,
- price=price,
- position=ticket,
- symbol=symbol,
- volume=volume,
- type=order_type.opposite,
- )
+ order = Order(action=TradeAction.DEAL, price=price, position=ticket, symbol=symbol, volume=volume, type=order_type.opposite)
return await order.send()
async def close_position_by_ticket(self, *, ticket: int) -> OrderSendResult | None:
@@ -127,14 +119,5 @@ class Positions:
int: Return number of positions closed.
"""
positions = self.positions or await self.get_positions()
- results = await asyncio.gather(
- *(self.close_position(position=position) for position in positions),
- return_exceptions=True,
- )
- return len(
- [
- res
- for res in results
- if (isinstance(res, OrderSendResult) and res.retcode == 10009)
- ]
- )
+ results = await asyncio.gather(*(self.close_position(position=position) for position in positions), return_exceptions=True)
+ return len([res for res in results if (isinstance(res, OrderSendResult) and res.retcode == 10009)])
diff --git a/src/aiomql/lib/ram.py b/src/aiomql/lib/ram.py
index 9ea22a6..9656de6 100644
--- a/src/aiomql/lib/ram.py
+++ b/src/aiomql/lib/ram.py
@@ -1,4 +1,5 @@
"""Risk Assessment and Management"""
+
from .account import Account
from .positions import Positions
diff --git a/src/aiomql/lib/result.py b/src/aiomql/lib/result.py
index 98495ae..c8a8d10 100644
--- a/src/aiomql/lib/result.py
+++ b/src/aiomql/lib/result.py
@@ -35,12 +35,8 @@ class Result:
self.name = name or self.parameters.get("name", "Trades")
def get_data(self) -> dict:
- res = self.result.get_dict(
- exclude={"retcode", "comment", "retcode_external", "request_id", "request"}
- )
- return (
- self.parameters | res | {"actual_profit": 0, "closed": False, "win": False}
- )
+ res = self.result.get_dict(exclude={"retcode", "comment", "retcode_external", "request_id", "request"})
+ return self.parameters | res | {"actual_profit": 0, "closed": False, "win": False}
async def save(self, *, trade_record_mode: Literal["csv", "json"] = None):
"""Record trade results as a csv or json file
@@ -71,9 +67,7 @@ class Result:
headers.update(data.keys())
read_file.close()
with file.open("w", newline="") as write_file:
- writer = csv.DictWriter(
- write_file, fieldnames=headers, restval=None, extrasaction="ignore"
- )
+ writer = csv.DictWriter(write_file, fieldnames=headers, restval=None, extrasaction="ignore")
writer.writeheader()
writer.writerows(rows)
except Exception as err:
diff --git a/src/aiomql/lib/sessions.py b/src/aiomql/lib/sessions.py
index 8c5d063..b33b6c5 100644
--- a/src/aiomql/lib/sessions.py
+++ b/src/aiomql/lib/sessions.py
@@ -23,12 +23,7 @@ def delta(obj: time) -> timedelta:
Args:
obj (datetime.time): A datetime.time object.
"""
- return timedelta(
- hours=obj.hour,
- minutes=obj.minute,
- seconds=obj.second,
- microseconds=obj.microsecond,
- )
+ return timedelta(hours=obj.hour, minutes=obj.minute, seconds=obj.second, microseconds=obj.microsecond)
async def backtest_sleep(secs):
@@ -58,9 +53,7 @@ class Session:
*,
start: int | time,
end: int | time,
- on_start: Literal[
- "close_all", "close_win", "close_loss", "custom_start"
- ] = None,
+ on_start: Literal["close_all", "close_win", "close_loss", "custom_start"] = None,
on_end: Literal["close_all", "close_win", "close_loss", "custom_end"] = None,
custom_start: Callable = None,
custom_end: Callable = None,
@@ -79,11 +72,7 @@ class Session:
custom_end (Callable): A custom function to call when the session ends. Default is None.
name (str): A name for the session. Default is a combination of start and end.
"""
- self.start = (
- start.replace(tzinfo=UTC)
- if isinstance(start, time)
- else time(hour=start, tzinfo=UTC)
- )
+ self.start = start.replace(tzinfo=UTC) if isinstance(start, time) else time(hour=start, tzinfo=UTC)
self.end = end if isinstance(end, time) else time(hour=end, tzinfo=UTC)
self.on_start = on_start
self.on_end = on_end
@@ -112,9 +101,7 @@ class Session:
now = (
datetime.now(tz=UTC).time()
if self.config.mode == "live"
- else datetime.fromtimestamp(
- self.config.backtest_engine.cursor.time, tz=UTC
- ).time()
+ else datetime.fromtimestamp(self.config.backtest_engine.cursor.time, tz=UTC).time()
)
return now in self
@@ -133,13 +120,7 @@ class Session:
return Duration(hours=hours, minutes=minutes, seconds=seconds)
async def close_positions(self, *, positions: tuple[TradePosition, ...]):
- results = asyncio.gather(
- *(
- self.positions_manager.close_position(position=position)
- for position in positions
- ),
- return_exceptions=True,
- )
+ results = asyncio.gather(*(self.positions_manager.close_position(position=position) for position in positions), return_exceptions=True)
closed = pending = 0
for result in results:
if isinstance(result, OrderSendResult) and result.retcode == 10009:
@@ -155,16 +136,12 @@ class Session:
async def close_win(self):
open_positions = await self.positions_manager.get_positions()
- positions = tuple(
- position for position in open_positions if position.profit >= 0
- )
+ positions = tuple(position for position in open_positions if position.profit >= 0)
await self.close_positions(positions=positions)
async def close_loss(self):
open_positions = await self.positions_manager.get_positions()
- positions = tuple(
- position for position in open_positions if position.profit < 0
- )
+ positions = tuple(position for position in open_positions if position.profit < 0)
await self.close_positions(positions=positions)
async def action(self, *, action):
@@ -198,9 +175,7 @@ class Session:
def until(self):
"""Get the seconds until the session starts from the current time in seconds."""
if self.config.mode == "backtest":
- now = datetime.fromtimestamp(
- self.config.backtest_engine.cursor.time, tz=UTC
- ).time()
+ now = datetime.fromtimestamp(self.config.backtest_engine.cursor.time, tz=UTC).time()
secs = (delta(self.start) - delta(now)).seconds
else:
secs = (delta(self.start) - delta(datetime.now(tz=UTC).time())).seconds
@@ -237,11 +212,7 @@ class Sessions:
moment = (
moment or datetime.now(tz=UTC).time()
if self.config.mode == "live"
- else (
- datetime.fromtimestamp(
- self.config.backtest_engine.cursor.time, tz=UTC
- ).time()
- )
+ else (datetime.fromtimestamp(self.config.backtest_engine.cursor.time, tz=UTC).time())
)
for session in self.sessions:
if moment in session:
@@ -260,11 +231,7 @@ class Sessions:
moment = (
moment or datetime.now(tz=UTC).time()
if self.config.mode == "live"
- else (
- datetime.fromtimestamp(
- self.config.backtest_engine.cursor.time, tz=UTC
- ).time()
- )
+ else (datetime.fromtimestamp(self.config.backtest_engine.cursor.time, tz=UTC).time())
)
for session in self.sessions:
if delta(moment) < delta(session.start):
@@ -287,9 +254,7 @@ class Sessions:
return
if self.config.mode == "backtest":
- now = datetime.fromtimestamp(
- self.config.backtest_engine.cursor.time, tz=UTC
- ).time()
+ now = datetime.fromtimestamp(self.config.backtest_engine.cursor.time, tz=UTC).time()
else:
now = datetime.now(tz=UTC).time()
diff --git a/src/aiomql/lib/strategy.py b/src/aiomql/lib/strategy.py
index b4f6dc5..4f078bf 100644
--- a/src/aiomql/lib/strategy.py
+++ b/src/aiomql/lib/strategy.py
@@ -1,4 +1,5 @@
"""The base class for creating strategies."""
+
import asyncio
from time import time
from typing import TypeVar
@@ -46,9 +47,7 @@ class Strategy(ABC):
backtest_controller = BackTestController
current_session = Session
- def __init__(
- self, *, symbol: Symbol, params: dict = None, sessions: Sessions = None, name=""
- ):
+ def __init__(self, *, symbol: Symbol, params: dict = None, sessions: Sessions = None, name=""):
"""Initiate the parameters dict and add name and symbol fields.
Use class name as strategy name if name is not provided
@@ -62,9 +61,7 @@ class Strategy(ABC):
self.parameters["symbol"] = symbol.name
self.parameters["name"] = self.name
self.running = True
- self.sessions = sessions or Sessions(
- sessions=[Session(start=0, end=dtime(hour=23, minute=59, second=59))]
- )
+ self.sessions = sessions or Sessions(sessions=[Session(start=0, end=dtime(hour=23, minute=59, second=59))])
self.config = Config()
self.mt5 = MetaTrader() if self.config.mode != "backtest" else MetaBackTester()
self.backtest_controller = BackTestController()
@@ -165,9 +162,7 @@ class Strategy(ABC):
async def backtest_strategy(self):
"""Backtest the strategy."""
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)
while self.running:
try:
await self.sessions.check()
diff --git a/src/aiomql/lib/symbol.py b/src/aiomql/lib/symbol.py
index 7bf1cc1..a67f944 100644
--- a/src/aiomql/lib/symbol.py
+++ b/src/aiomql/lib/symbol.py
@@ -1,4 +1,5 @@
"""Symbol class for handling a financial instrument."""
+
from datetime import datetime
from logging import getLogger
@@ -157,10 +158,7 @@ class Symbol(_Base, SymbolInfo):
if check := self.volume_min <= volume <= self.volume_max:
return check, volume
else:
- return (
- check,
- self.volume_min if volume <= self.volume_min else self.volume_max,
- )
+ return (check, self.volume_min if volume <= self.volume_min else self.volume_max)
def round_off_volume(self, *, volume: float, round_down: bool = False) -> float:
"""Round off the volume to the nearest volume step.
@@ -177,11 +175,7 @@ class Symbol(_Base, SymbolInfo):
async def amount_in_quote_currency(self, *, amount: float) -> float:
"""Convert the amount to the quote currency of the symbol."""
if self.currency_profit != self.account.currency:
- amount = await self.convert_currency(
- amount=amount,
- from_currency=self.account.currency,
- to_currency=self.currency_profit,
- )
+ amount = await self.convert_currency(amount=amount, from_currency=self.account.currency, to_currency=self.currency_profit)
return amount
async def compute_volume(self) -> float:
@@ -194,9 +188,7 @@ class Symbol(_Base, SymbolInfo):
"""
return self.volume_min
- async def convert_currency(
- self, *, amount: float, from_currency: str, to_currency: str
- ) -> float:
+ async def convert_currency(self, *, amount: float, from_currency: str, to_currency: str) -> float:
"""Convert a given amount from one currency to the other.
Args:
amount: Amount to convert
@@ -215,14 +207,10 @@ class Symbol(_Base, SymbolInfo):
if tick is not None:
return round(amount / tick.ask, 2)
except Exception as err:
- logger.warning(
- f"{err}: Currency conversion failed: Unable to convert {amount} in {quote} to {base}"
- )
+ logger.warning(f"{err}: Currency conversion failed: Unable to convert {amount} in {quote} to {base}")
@backoff_decorator
- async def copy_rates_from(
- self, *, timeframe: TimeFrame, date_from: datetime | int, count: int = 500
- ) -> Candles:
+ async def copy_rates_from(self, *, timeframe: TimeFrame, date_from: datetime | int, count: int = 500) -> Candles:
"""
Get bars from the MetaTrader 5 terminal starting from the specified date.
@@ -246,9 +234,7 @@ class Symbol(_Base, SymbolInfo):
raise ValueError(f"Could not get rates for {self.name}.")
@backoff_decorator
- async def copy_rates_from_pos(
- self, *, timeframe: TimeFrame, count: int = 500, start_position: int = 0
- ) -> Candles:
+ async def copy_rates_from_pos(self, *, timeframe: TimeFrame, count: int = 500, start_position: int = 0) -> Candles:
"""Get bars from the MetaTrader 5 terminal starting from the specified index.
Args:
@@ -265,21 +251,13 @@ class Symbol(_Base, SymbolInfo):
Raises:
ValueError: If request was unsuccessful and None was returned
"""
- rates = await self.mt5.copy_rates_from_pos(
- self.name, timeframe, start_position, count
- )
+ rates = await self.mt5.copy_rates_from_pos(self.name, timeframe, start_position, count)
if rates is not None:
return Candles(data=rates)
raise ValueError(f"Could not get rates for {self.name}.")
@backoff_decorator
- async def copy_rates_range(
- self,
- *,
- timeframe: TimeFrame,
- date_from: datetime | int,
- date_to: datetime | int,
- ) -> Candles:
+ async def copy_rates_range(self, *, timeframe: TimeFrame, date_from: datetime | int, date_to: datetime | int) -> Candles:
"""Get bars in the specified date range from the MetaTrader 5 terminal.
Args:
@@ -299,21 +277,13 @@ class Symbol(_Base, SymbolInfo):
Raises:
ValueError: If request was unsuccessful and None was returned
"""
- rates = await self.mt5.copy_rates_range(
- symbol=self.name, timeframe=timeframe, date_from=date_from, date_to=date_to
- )
+ rates = await self.mt5.copy_rates_range(symbol=self.name, timeframe=timeframe, date_from=date_from, date_to=date_to)
if rates is not None:
return Candles(data=rates)
raise ValueError(f"Could not get rates for {self.name}.")
@backoff_decorator
- async def copy_ticks_from(
- self,
- *,
- date_from: datetime | int,
- count: int = 100,
- flags: CopyTicks = CopyTicks.ALL,
- ) -> Ticks:
+ async def copy_ticks_from(self, *, date_from: datetime | int, count: int = 100, flags: CopyTicks = CopyTicks.ALL) -> Ticks:
"""
Get ticks from the MetaTrader 5 terminal starting from the specified date.
@@ -336,13 +306,7 @@ class Symbol(_Base, SymbolInfo):
raise ValueError(f"Could not get ticks for {self.name}.")
@backoff_decorator
- async def copy_ticks_range(
- self,
- *,
- date_from: datetime | int,
- date_to: datetime | int,
- flags: CopyTicks = CopyTicks.ALL,
- ) -> Ticks:
+ async def copy_ticks_range(self, *, date_from: datetime | int, date_to: datetime | int, flags: CopyTicks = CopyTicks.ALL) -> Ticks:
"""Get ticks for the specified date range from the MetaTrader 5 terminal.
Args:
diff --git a/src/aiomql/lib/terminal.py b/src/aiomql/lib/terminal.py
index bf61159..d380c87 100644
--- a/src/aiomql/lib/terminal.py
+++ b/src/aiomql/lib/terminal.py
@@ -1,4 +1,5 @@
"""Terminal related functions and properties"""
+
from typing import NamedTuple
from logging import getLogger
@@ -7,9 +8,7 @@ from ..core.base import _Base
logger = getLogger(__name__)
-Version = NamedTuple(
- "Version", (("version", str), ("build", int), ("release_date", str))
-)
+Version = NamedTuple("Version", (("version", str), ("build", int), ("release_date", str)))
class Terminal(_Base, TerminalInfo):
diff --git a/src/aiomql/lib/ticks.py b/src/aiomql/lib/ticks.py
index 9b494d4..8179e02 100644
--- a/src/aiomql/lib/ticks.py
+++ b/src/aiomql/lib/ticks.py
@@ -38,27 +38,22 @@ class Tick:
"""Initialize the Tick class. Set attributes from keyword arguments. bid, ask, last and volume must be
present"""
if not all(key in kwargs for key in ["bid", "ask", "last", "volume"]):
- raise ValueError(
- "bid, ask, last and volume, time must be present in the keyword arguments"
- )
+ raise ValueError("bid, ask, last and volume, time must be present in the keyword arguments")
self.Index = kwargs.pop("Index", 0)
self.time = kwargs.pop("time", time.monotonic())
self.time_msc = int(self.time * 1000)
self.set_attributes(**kwargs)
def __repr__(self):
- return (
- "%(class)s(Index=%(Index)s, time=%(time)s, bid=%(bid)s, ask=%(ask)s, last=%(last)s, volume=%(volume)s)"
- % {
- "class": self.__class__.__name__,
- "time": self.time,
- "bid": self.bid,
- "ask": self.ask,
- "last": self.last,
- "volume": self.volume,
- "Index": self.Index,
- }
- )
+ return "%(class)s(Index=%(Index)s, time=%(time)s, bid=%(bid)s, ask=%(ask)s, last=%(last)s, volume=%(volume)s)" % {
+ "class": self.__class__.__name__,
+ "time": self.time,
+ "bid": self.bid,
+ "ask": self.ask,
+ "last": self.last,
+ "volume": self.volume,
+ "Index": self.Index,
+ }
def __eq__(self, other: Self):
return self.time == other.time
@@ -148,9 +143,7 @@ class Ticks:
def __getattr__(self, item):
if item in list(self._data.columns.values):
return self._data[item]
- raise AttributeError(
- f"Attribute {item} not defined on class {self.__class__.__name__}"
- )
+ raise AttributeError(f"Attribute {item} not defined on class {self.__class__.__name__}")
def __getitem__(self, index) -> Tick | Self:
if isinstance(index, slice):
diff --git a/src/aiomql/lib/trade_records.py b/src/aiomql/lib/trade_records.py
index 650a0b0..fcfd214 100644
--- a/src/aiomql/lib/trade_records.py
+++ b/src/aiomql/lib/trade_records.py
@@ -73,12 +73,7 @@ class TradeRecords:
rows = await self.update_rows(rows=rows)
with open(file, mode="w", newline="") as fw:
- writer = csv.DictWriter(
- fw,
- fieldnames=reader.fieldnames,
- extrasaction="ignore",
- restval=None,
- )
+ writer = csv.DictWriter(fw, fieldnames=reader.fieldnames, extrasaction="ignore", restval=None)
writer.writeheader()
writer.writerows(rows)
except Exception as err:
@@ -119,12 +114,7 @@ class TradeRecords:
deals = [
deal
for deal in deals
- if (
- deal.order != deal.position_id
- and deal.position_id == order
- and deal.entry == 1
- and deal.position_id not in position_ids
- )
+ if (deal.order != deal.position_id and deal.position_id == order and deal.entry == 1 and deal.position_id not in position_ids)
]
deals.sort(key=lambda deal: deal.time_msc)
deal = deals[-1]
@@ -157,16 +147,12 @@ class TradeRecords:
async def update_csv_records(self):
"""Update csv trade records in the records_dir folder."""
- records = [
- self.read_update_csv(file=record) for record in self.get_csv_records()
- ]
+ records = [self.read_update_csv(file=record) for record in self.get_csv_records()]
await asyncio.gather(*records)
async def update_json_records(self):
"""Update json trade records in the records_dir folder."""
- records = [
- self.read_update_json(file=record) for record in self.get_json_records()
- ]
+ records = [self.read_update_json(file=record) for record in self.get_json_records()]
await asyncio.gather(*records)
async def update_csv_record(self, *, file: Path | str):
diff --git a/src/aiomql/lib/trader.py b/src/aiomql/lib/trader.py
index 2bce467..b814427 100644
--- a/src/aiomql/lib/trader.py
+++ b/src/aiomql/lib/trader.py
@@ -60,17 +60,11 @@ class Trader(ABC):
sl, tp = pips, pips * (risk_to_reward or self.ram.risk_to_reward)
price = self.order.price
if self.order.type == OrderType.BUY:
- self.order.sl, self.order.tp = round(price - sl, self.symbol.digits), round(
- price + tp, self.symbol.digits
- )
+ self.order.sl, self.order.tp = round(price - sl, self.symbol.digits), round(price + tp, self.symbol.digits)
elif self.order.type == OrderType.SELL:
- self.order.sl, self.order.tp = round(price + sl, self.symbol.digits), round(
- price - tp, self.symbol.digits
- )
+ self.order.sl, self.order.tp = round(price + sl, self.symbol.digits), round(price - tp, self.symbol.digits)
- def set_trade_stop_levels_points(
- self, *, points: float, risk_to_reward: float = None
- ):
+ def set_trade_stop_levels_points(self, *, points: float, risk_to_reward: float = None):
"""Set the stop loss and take profit levels of the order based on the points and the risk to reward ratio.
It is assumed that order_type and price are already set before calling this method.
@@ -83,23 +77,12 @@ class Trader(ABC):
price, digits = self.order.price, self.symbol.digits
if self.order.type == OrderType.BUY:
- self.order.sl, self.order.tp = round(price - sl, self.symbol.digits), round(
- price + tp, digits
- )
+ self.order.sl, self.order.tp = round(price - sl, self.symbol.digits), round(price + tp, digits)
elif self.order.type == OrderType.SELL:
- self.order.sl, self.order.tp = round(price + sl, self.symbol.digits), round(
- price - tp, digits
- )
+ self.order.sl, self.order.tp = round(price + sl, self.symbol.digits), round(price - tp, digits)
- async def create_order_with_stops(
- self,
- *,
- order_type: OrderType,
- sl: float,
- tp: float,
- amount_to_risk: float = None,
- ):
+ async def create_order_with_stops(self, *, order_type: OrderType, sl: float, tp: float, amount_to_risk: float = None):
"""Create an order with stop loss and take profit levels. Use the amount to risk per trade to
calculate the volume.
@@ -115,18 +98,9 @@ class Trader(ABC):
tick = await self.symbol.info_tick()
price = tick.ask if order_type == OrderType.BUY else tick.bid
volume = await self.symbol.compute_volume_sl(amount=amount, price=price, sl=sl)
- self.order.set_attributes(
- sl=sl, tp=tp, volume=volume, price=price, type=order_type
- )
+ self.order.set_attributes(sl=sl, tp=tp, volume=volume, price=price, type=order_type)
- async def create_order_with_sl(
- self,
- *,
- order_type: OrderType,
- sl: float,
- amount_to_risk: float = None,
- risk_to_reward: float = None,
- ):
+ async def create_order_with_sl(self, *, order_type: OrderType, sl: float, amount_to_risk: float = None, risk_to_reward: float = None):
"""
Create an order with a given stop_loss level. Use the amount to risk per trade to calculate the volume.
@@ -146,18 +120,9 @@ class Trader(ABC):
dtp = dsl * (risk_to_reward or self.ram.risk_to_reward)
tp = price + dtp if order_type == OrderType.BUY else price - dtp
volume = await self.symbol.compute_volume_sl(amount=amount, price=price, sl=sl)
- self.order.set_attributes(
- sl=sl, tp=tp, volume=volume, price=price, type=order_type
- )
+ self.order.set_attributes(sl=sl, tp=tp, volume=volume, price=price, type=order_type)
- async def create_order_with_points(
- self,
- *,
- order_type: OrderType,
- points: float,
- amount_to_risk: float = None,
- risk_to_reward: float = None,
- ):
+ async def create_order_with_points(self, *, order_type: OrderType, points: float, amount_to_risk: float = None, risk_to_reward: float = None):
"""Create an order with specific points to risk. Use the amount to risk per trade to calculate the volume.
Args:
@@ -218,9 +183,7 @@ class Trader(ABC):
return result
@error_handler
- async def record_trade(
- self, *, result: OrderSendResult, parameters: dict = None, name: str = ""
- ):
+ async def record_trade(self, *, result: OrderSendResult, parameters: dict = None, name: str = ""):
"""Record the trade in csv or json.
Args:
result (OrderSendResult): Result of the order send
@@ -232,11 +195,7 @@ class Trader(ABC):
params = {**parameters} or {}
profit = await self.order.calc_profit()
params["expected_profit"] = profit
- date = (
- datetime.now(tz=UTC)
- if self.config.mode == "live"
- else datetime.fromtimestamp(self.config.backtest_engine.cursor.time, tz=UTC)
- )
+ date = datetime.now(tz=UTC) if self.config.mode == "live" else datetime.fromtimestamp(self.config.backtest_engine.cursor.time, tz=UTC)
params["date"] = date.strftime("%Y-%m-%d %H:%M:%S.%f")
res = Result(result=result, parameters=params, name=name)
self.config.task_queue.add(item=QueueItem(res.save), must_complete=True)
diff --git a/tests/backtest/conftest.py b/tests/backtest/conftest.py
index b4952e2..4463682 100644
--- a/tests/backtest/conftest.py
+++ b/tests/backtest/conftest.py
@@ -32,11 +32,7 @@ async def close_all_positions():
positions = await mt.positions_get()
tasks = []
for position in positions:
- order_type = (
- mt.ORDER_TYPE_BUY
- if position.type == mt.ORDER_TYPE_SELL
- else mt.ORDER_TYPE_SELL
- )
+ order_type = mt.ORDER_TYPE_BUY if position.type == mt.ORDER_TYPE_SELL else mt.ORDER_TYPE_SELL
req = {
"action": mt.TRADE_ACTION_DEAL,
"symbol": position.symbol,
@@ -54,9 +50,7 @@ async def close_all_positions():
@pytest.fixture(scope="package", autouse=True)
async def config(request):
Path("tests/backtest/configs").mkdir(exist_ok=True)
- with open("aiomql.json", "r") as fh, open(
- "tests/backtest/configs/test2.json", "w"
- ) as fh1, open("tests/backtest/test.json", "w") as fh2:
+ with open("aiomql.json", "r") as fh, open("tests/backtest/configs/test2.json", "w") as fh1, open("tests/backtest/test.json", "w") as fh2:
data = json.load(fh)
data["mode"] = "backtest"
json.dump(data, fh1, indent=2)
@@ -77,19 +71,14 @@ async def mt():
@pytest.fixture(scope="package")
async def period():
- return {
- "start": datetime(2024, 2, 1, hour=8, tzinfo=UTC),
- "end": datetime(2024, 2, 7, hour=16, tzinfo=UTC),
- }
+ return {"start": datetime(2024, 2, 1, hour=8, tzinfo=UTC), "end": datetime(2024, 2, 7, hour=16, tzinfo=UTC)}
@pytest.fixture(scope="package")
async def backtest_engine(period):
start = period["start"]
end = period["end"]
- return BackTestEngine(
- start=start, end=end, name="backtest_data", assign_to_config=True, preload=False
- )
+ return BackTestEngine(start=start, end=end, name="backtest_data", assign_to_config=True, preload=False)
@pytest.fixture(scope="function")
diff --git a/tests/backtest/integration/test_backtesting.py b/tests/backtest/integration/test_backtesting.py
index ef722fd..03c854c 100644
--- a/tests/backtest/integration/test_backtesting.py
+++ b/tests/backtest/integration/test_backtesting.py
@@ -27,9 +27,7 @@ async def make_buy_sell_orders():
return {"buy": Order(**buy_req), "sell": Order(**sell_req)}
-def test_trade_mode(
- config, backtest_engine, history, positions, order_sell, order_buy, btc_usd
-):
+def test_trade_mode(config, backtest_engine, history, positions, order_sell, order_buy, btc_usd):
assert config.mode == "backtest"
assert isinstance(backtest_engine, BackTestEngine)
assert isinstance(history.mt5, MetaBackTester)
@@ -79,26 +77,16 @@ async def test_history(backtest_engine, history, order_sell, order_buy, position
async def test_margin(backtest_engine, order_sell, order_buy):
await backtest_engine.setup_account(balance=100)
so_margin = await backtest_engine.order_calc_margin(
- action=order_sell.action,
- volume=order_sell.volume,
- symbol=order_sell.symbol,
- price=order_sell.price,
+ action=order_sell.action, volume=order_sell.volume, symbol=order_sell.symbol, price=order_sell.price
)
bo_margin = await backtest_engine.order_calc_margin(
- action=order_buy.action,
- volume=order_buy.volume,
- symbol=order_buy.symbol,
- price=order_buy.price,
+ action=order_buy.action, volume=order_buy.volume, symbol=order_buy.symbol, price=order_buy.price
)
total_margin = so_margin + bo_margin
await backtest_engine.order_send(request=order_sell.request)
await backtest_engine.order_send(request=order_buy.request)
# noinspection PyTestUnpassedFixture
- assert (
- backtest_engine.positions.margin
- == total_margin
- == backtest_engine._account.margin
- )
+ assert backtest_engine.positions.margin == total_margin == backtest_engine._account.margin
backtest_engine.reset(clear_data=True)
@@ -124,11 +112,7 @@ async def test_account(backtest_engine, positions):
deal = backtest_engine.deals.history_deals_get(position=bo.order)
bo_profit = deal[-1].profit
assert len(all_pos) == 1
- assert (
- backtest_engine.positions.margin
- == backtest_engine._account.margin
- == backtest_engine.positions.margins[so.order]
- )
+ assert backtest_engine.positions.margin == backtest_engine._account.margin == backtest_engine.positions.margins[so.order]
profit = sum([pos.profit for pos in all_pos])
n_balance = backtest_engine._account.balance
n_equity = backtest_engine._account.equity
@@ -155,9 +139,7 @@ async def test_wrapup(positions, buy_order, sell_order, backtest_engine, config)
last_equity = backtest_engine._account.equity
last_profit = backtest_engine._account.profit
tdata = GetData.load_data(name=config.backtest_dir / f"{backtest_engine.name}.pkl")
- new_bte = BackTestEngine(
- data=tdata, restart=False, assign_to_config=False, preload=False
- )
+ new_bte = BackTestEngine(data=tdata, restart=False, assign_to_config=False, preload=False)
assert new_bte._account.balance == last_balance
assert new_bte._account.equity == last_equity
assert new_bte._account.profit == last_profit
diff --git a/tests/backtest/unit/test_deals_manager.py b/tests/backtest/unit/test_deals_manager.py
index 4f3d13a..ba69b33 100644
--- a/tests/backtest/unit/test_deals_manager.py
+++ b/tests/backtest/unit/test_deals_manager.py
@@ -21,8 +21,4 @@ async def test_deals_manager(backtest_engine, sell_order, buy_order, period, pos
deals = backtest_engine.deals.history_deals_get(position=bo.order)
assert len(deals) <= 2
orders = backtest_engine.deals.get_deals_range(date_from=start, date_to=end)
- assert (
- len(orders)
- == backtest_engine.deals.history_deals_total(date_from=start, date_to=end)
- == len(backtest_engine.deals._data.keys())
- )
+ assert len(orders) == backtest_engine.deals.history_deals_total(date_from=start, date_to=end) == len(backtest_engine.deals._data.keys())
diff --git a/tests/backtest/unit/test_order_manager.py b/tests/backtest/unit/test_order_manager.py
index 0be0bf3..b5d577e 100644
--- a/tests/backtest/unit/test_order_manager.py
+++ b/tests/backtest/unit/test_order_manager.py
@@ -1,7 +1,5 @@
# noinspection PyTestUnpassedFixture
-async def test_orders_manager(
- backtest_engine, sell_order, buy_order, period, positions
-):
+async def test_orders_manager(backtest_engine, sell_order, buy_order, period, positions):
backtest_engine.reset(clear_data=True)
await backtest_engine.setup_account(balance=100)
backtest_engine.fast_forward(steps=100)
@@ -23,8 +21,4 @@ async def test_orders_manager(
orders = backtest_engine.orders.history_orders_get(position=bo.order)
assert len(orders) <= 2
orders = backtest_engine.orders.get_orders_range(date_from=start, date_to=end)
- assert (
- len(orders)
- == backtest_engine.orders.history_orders_total(date_from=start, date_to=end)
- == len(backtest_engine.orders._data.keys())
- )
+ assert len(orders) == backtest_engine.orders.history_orders_total(date_from=start, date_to=end) == len(backtest_engine.orders._data.keys())
diff --git a/tests/live/conftest.py b/tests/live/conftest.py
index 5f48369..4ff4fb4 100644
--- a/tests/live/conftest.py
+++ b/tests/live/conftest.py
@@ -29,11 +29,7 @@ async def close_all_positions():
positions = await mt.positions_get()
tasks = []
for position in positions:
- order_type = (
- mt.ORDER_TYPE_BUY
- if position.type == mt.ORDER_TYPE_SELL
- else mt.ORDER_TYPE_SELL
- )
+ order_type = mt.ORDER_TYPE_BUY if position.type == mt.ORDER_TYPE_SELL else mt.ORDER_TYPE_SELL
req = {
"action": mt.TRADE_ACTION_DEAL,
"symbol": position.symbol,
@@ -51,9 +47,7 @@ async def close_all_positions():
@pytest.fixture(scope="package", autouse=True)
async def config(request):
Path("tests/live/configs").mkdir(exist_ok=True)
- with open("aiomql.json", "r") as fh, open(
- "tests/live/configs/test2.json", "w"
- ) as fh1, open("tests/live/test.json", "w") as fh2:
+ with open("aiomql.json", "r") as fh, open("tests/live/configs/test2.json", "w") as fh1, open("tests/live/test.json", "w") as fh2:
data = json.load(fh)
json.dump(data, fh1, indent=2)
json.dump(data, fh2, indent=2)
diff --git a/tests/live/integration/test_bot.py b/tests/live/integration/test_bot.py
index bbc679d..af471d4 100644
--- a/tests/live/integration/test_bot.py
+++ b/tests/live/integration/test_bot.py
@@ -6,10 +6,7 @@ from aiomql.contrib.symbols import ForexSymbol
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"]
symbols = [ForexSymbol(name=sym) for sym in syms]
strategies = [Chaos(symbol=symbol, name="test_chaos") for symbol in symbols]
diff --git a/tests/live/integration/test_bot_sync.py b/tests/live/integration/test_bot_sync.py
index 9bf8a75..b955be2 100644
--- a/tests/live/integration/test_bot_sync.py
+++ b/tests/live/integration/test_bot_sync.py
@@ -6,10 +6,7 @@ from aiomql.contrib.symbols import ForexSymbol
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"]
symbols = [ForexSymbol(name=sym) for sym in syms]
strategies = [Chaos(symbol=symbol, name="test_chaos") for symbol in symbols]
diff --git a/tests/live/integration/test_results_records.py b/tests/live/integration/test_results_records.py
index 9278bcb..e7a69cf 100644
--- a/tests/live/integration/test_results_records.py
+++ b/tests/live/integration/test_results_records.py
@@ -36,13 +36,7 @@ class TestRecordsAndResults:
async def sell(self, mt):
sym = "BTCUSD"
sym_info = await mt.symbol_info(sym)
- return {
- "action": mt.TRADE_ACTION_DEAL,
- "symbol": sym,
- "volume": sym_info.volume_min,
- "type": mt.ORDER_TYPE_SELL,
- "price": sym_info.bid,
- }
+ return {"action": mt.TRADE_ACTION_DEAL, "symbol": sym, "volume": sym_info.volume_min, "type": mt.ORDER_TYPE_SELL, "price": sym_info.bid}
@pytest.fixture(scope="class", autouse=True)
async def setup(self, sell, buy, mt):
@@ -50,24 +44,11 @@ class TestRecordsAndResults:
buy_res_2 = await mt.order_send(buy)
sell_res = await mt.order_send(sell)
sell_res_2 = await mt.order_send(sell)
- buy_res = Result(
- result=OrderSendResult(**buy_res._asdict()), name="test_result"
- )
- sell_res = Result(
- result=OrderSendResult(**sell_res._asdict()), name="test_result"
- )
- sell_res_2 = Result(
- result=OrderSendResult(**sell_res_2._asdict()), name="test_result"
- )
- buy_res_2 = Result(
- result=OrderSendResult(**buy_res_2._asdict()), name="test_result"
- )
- await asyncio.gather(
- buy_res.save(),
- sell_res.save(),
- buy_res_2.save(trade_record_mode="json"),
- sell_res_2.save(trade_record_mode="json"),
- )
+ buy_res = Result(result=OrderSendResult(**buy_res._asdict()), name="test_result")
+ sell_res = Result(result=OrderSendResult(**sell_res._asdict()), name="test_result")
+ sell_res_2 = Result(result=OrderSendResult(**sell_res_2._asdict()), name="test_result")
+ buy_res_2 = Result(result=OrderSendResult(**buy_res_2._asdict()), name="test_result")
+ await asyncio.gather(buy_res.save(), sell_res.save(), buy_res_2.save(trade_record_mode="json"), sell_res_2.save(trade_record_mode="json"))
await Positions().close_all()
def test_records_dir(self):
@@ -90,9 +71,7 @@ class TestRecordsAndResults:
async def test_json_records(self):
json_records = self.trade_records.get_json_records()
- matched_recs = [
- record for record in json_records if record.match("test_result.json")
- ]
+ matched_recs = [record for record in json_records if record.match("test_result.json")]
assert len(matched_recs) == 1
record = matched_recs[0]
record_data = json.load(record.open())
@@ -106,9 +85,7 @@ class TestRecordsAndResults:
async def test_csv_records(self):
csv_records = self.trade_records.get_csv_records()
- matched_recs = [
- record for record in csv_records if record.match("test_result.csv")
- ]
+ matched_recs = [record for record in csv_records if record.match("test_result.csv")]
assert len(matched_recs) == 1
record = matched_recs[0]
record_data = DictReader(record.open())
diff --git a/tests/live/unit/test_backtest_engine.py b/tests/live/unit/test_backtest_engine.py
index 9b83a03..22255f0 100644
--- a/tests/live/unit/test_backtest_engine.py
+++ b/tests/live/unit/test_backtest_engine.py
@@ -14,40 +14,20 @@ class TestBackTestEngine:
def setup_class(cls):
cls.start = datetime(2024, 2, 1)
cls.end = datetime(2024, 2, 7)
- cls.g_data = GetData(
- start=cls.start,
- end=cls.end,
- symbols=["BTCUSD", "SOLUSD"],
- timeframes=[TimeFrame.H1, TimeFrame.H2],
- name="test_engine",
- )
- cls.bte = BackTestEngine(
- start=cls.start, end=cls.end, assign_to_config=True, preload=False
- )
+ cls.g_data = GetData(start=cls.start, end=cls.end, symbols=["BTCUSD", "SOLUSD"], timeframes=[TimeFrame.H1, TimeFrame.H2], name="test_engine")
+ cls.bte = BackTestEngine(start=cls.start, end=cls.end, assign_to_config=True, preload=False)
@pytest.fixture(scope="class")
async def bte2(self):
await self.g_data.get_data()
- bte2 = BackTestEngine(
- start=self.start,
- end=self.end,
- data=self.g_data.data,
- use_terminal=False,
- preload=False,
- )
+ bte2 = BackTestEngine(start=self.start, end=self.end, data=self.g_data.data, use_terminal=False, preload=False)
await bte2.setup_account(balance=100)
return bte2
@pytest.fixture(scope="class")
async def sell_order(self):
sym = await self.bte.get_symbol_info(symbol="BTCUSD")
- request = {
- "type": OrderType.SELL,
- "symbol": "BTCUSD",
- "volume": sym.volume_min,
- "price": sym.bid,
- "action": TradeAction.DEAL,
- }
+ request = {"type": OrderType.SELL, "symbol": "BTCUSD", "volume": sym.volume_min, "price": sym.bid, "action": TradeAction.DEAL}
return request
@pytest.fixture(scope="class")
@@ -67,16 +47,11 @@ class TestBackTestEngine:
}
return request
- def modify_stops(self, order):
- ...
+ def modify_stops(self, order): ...
def test_span_and_range(self):
- assert self.bte.range == range(
- 0, int((self.end - self.start).total_seconds()), self.bte.speed
- )
- assert self.bte.span == range(
- int(self.start.timestamp()), int(self.end.timestamp()), self.bte.speed
- )
+ assert self.bte.range == range(0, int((self.end - self.start).total_seconds()), self.bte.speed)
+ assert self.bte.span == range(int(self.start.timestamp()), int(self.end.timestamp()), self.bte.speed)
assert len(self.bte.span) == len(self.bte.range)
def test_cursor(self):
@@ -158,16 +133,12 @@ class TestBackTestEngine:
async def test_get_rates_from(self):
start = datetime(2024, 2, 3, 12, 43, tzinfo=UTC)
- rates = await self.bte.get_rates_from(
- symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, count=24
- )
+ rates = await self.bte.get_rates_from(symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, count=24)
assert len(rates) == 24
async def test_get_rates_from_2(self, bte2):
start = datetime(2024, 2, 3, 12, 12, tzinfo=UTC)
- rates = await bte2.get_rates_from(
- symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, count=24
- )
+ rates = await bte2.get_rates_from(symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, count=24)
assert len(rates) == 24
async def test_get_rates_from_pos(self):
@@ -175,51 +146,37 @@ class TestBackTestEngine:
self.bte.go_to(time=now)
tf = TimeFrame.H2
start_pos = 2
- rates = await self.bte.get_rates_from_pos(
- symbol="BTCUSD", timeframe=tf, start_pos=start_pos, count=24
- )
+ rates = await self.bte.get_rates_from_pos(symbol="BTCUSD", timeframe=tf, start_pos=start_pos, count=24)
assert len(rates) == 24
- assert int(rates[-1][0]) == round_down(
- int(now.replace(hour=now.hour - start_pos).timestamp()), tf.seconds
- )
+ assert int(rates[-1][0]) == round_down(int(now.replace(hour=now.hour - start_pos).timestamp()), tf.seconds)
async def test_get_rates_from_pos2(self, bte2):
now = datetime(2024, 2, 4, 12, 15, tzinfo=UTC)
bte2.go_to(time=now)
tf = TimeFrame.H1
start_pos = 2
- rates = await bte2.get_rates_from_pos(
- symbol="BTCUSD", timeframe=tf, start_pos=start_pos, count=24
- )
- assert int(rates[-1][0]) == round_down(
- int(now.replace(hour=10).timestamp()), tf.seconds
- )
+ rates = await bte2.get_rates_from_pos(symbol="BTCUSD", timeframe=tf, start_pos=start_pos, count=24)
+ assert int(rates[-1][0]) == round_down(int(now.replace(hour=10).timestamp()), tf.seconds)
# assert int(rates[-1][0]) == round_up(int(now.timestamp()), tf.seconds) - start_pos * tf.seconds
assert len(rates) == 24
async def test_get_rates_range(self):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
end = datetime(2024, 2, 4, 18, tzinfo=UTC)
- rates = await self.bte.get_rates_range(
- symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, date_to=end
- )
+ rates = await self.bte.get_rates_range(symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, date_to=end)
assert len(rates) == 31
assert int(rates[-1][0]) == int(end.timestamp())
async def test_get_rates_range2(self, bte2):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
end = datetime(2024, 2, 4, 18, tzinfo=UTC)
- rates = await bte2.get_rates_range(
- symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, date_to=end
- )
+ rates = await bte2.get_rates_range(symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, date_to=end)
assert len(rates) == 31
assert int(rates[-1][0]) == int(end.timestamp())
async def test_get_ticks_from(self):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
- ticks = await self.bte.get_ticks_from(
- symbol="BTCUSD", date_from=start, count=24
- )
+ ticks = await self.bte.get_ticks_from(symbol="BTCUSD", date_from=start, count=24)
assert len(ticks) == 24
async def test_get_ticks_from2(self, bte2):
@@ -230,23 +187,15 @@ class TestBackTestEngine:
async def test_get_ticks_range(self):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
end = datetime(2024, 2, 3, 15, tzinfo=UTC)
- ticks = await self.bte.get_ticks_range(
- symbol="BTCUSD", date_from=start, date_to=end
- )
- approx_total = (
- end - start
- ).total_seconds() // 2 # assuming 2 ticks per second at least
+ ticks = await self.bte.get_ticks_range(symbol="BTCUSD", date_from=start, date_to=end)
+ approx_total = (end - start).total_seconds() // 2 # assuming 2 ticks per second at least
assert len(ticks) >= approx_total
async def test_get_ticks_range2(self, bte2):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
end = datetime(2024, 2, 3, 15, tzinfo=UTC)
- ticks = await bte2.get_ticks_range(
- symbol="BTCUSD", date_from=start, date_to=end
- )
- approx_total = (
- end - start
- ).total_seconds() // 2 # assuming 2 ticks per second at least
+ ticks = await bte2.get_ticks_range(symbol="BTCUSD", date_from=start, date_to=end)
+ approx_total = (end - start).total_seconds() // 2 # assuming 2 ticks per second at least
assert len(ticks) >= approx_total
async def test_price_tick(self, bte2):
@@ -287,22 +236,14 @@ class TestBackTestEngine:
tp = sym_info.ask + dsl
profit = await self.bte.order_calc_profit(
- action=OrderType.BUY,
- symbol=sym,
- volume=sym_info.volume_min,
- price_open=sym_info.ask,
- price_close=tp,
+ action=OrderType.BUY, symbol=sym, volume=sym_info.volume_min, price_open=sym_info.ask, price_close=tp
)
assert profit > 0
sym_info2 = await bte2.get_symbol_info(symbol=sym)
dsl2 = (sym_info2.trade_stops_level + sym_info2.spread) * 2 * sym_info2.point
tp2 = sym_info2.ask + dsl2
profit2 = await bte2.order_calc_profit(
- action=OrderType.BUY,
- symbol=sym,
- volume=sym_info2.volume_min,
- price_open=sym_info2.ask,
- price_close=tp2,
+ action=OrderType.BUY, symbol=sym, volume=sym_info2.volume_min, price_open=sym_info2.ask, price_close=tp2
)
assert ceil(profit) == ceil(profit2)
@@ -314,20 +255,10 @@ class TestBackTestEngine:
bte2.go_to(time=moment)
sym = "BTCUSD"
sym_info = await self.bte.get_symbol_info(symbol=sym)
- margin = await self.bte.order_calc_margin(
- action=OrderType.SELL,
- symbol=sym,
- volume=sym_info.volume_min,
- price=sym_info.bid,
- )
+ margin = await self.bte.order_calc_margin(action=OrderType.SELL, symbol=sym, volume=sym_info.volume_min, price=sym_info.bid)
assert margin > 0
sym_info2 = await self.bte.get_symbol_info(symbol=sym)
- margin2 = await bte2.order_calc_margin(
- action=OrderType.SELL,
- symbol=sym,
- volume=sym_info2.volume_min,
- price=sym_info2.bid,
- )
+ margin2 = await bte2.order_calc_margin(action=OrderType.SELL, symbol=sym, volume=sym_info2.volume_min, price=sym_info2.bid)
assert margin2 > 0
async def test_order_check(self, buy_order, sell_order):
diff --git a/tests/live/unit/test_candles.py b/tests/live/unit/test_candles.py
index f8825ca..934068a 100644
--- a/tests/live/unit/test_candles.py
+++ b/tests/live/unit/test_candles.py
@@ -11,12 +11,8 @@ from aiomql.core.constants import TimeFrame
class TestCandle:
@classmethod
def setup_class(cls):
- cls.bullish_candle = Candle(
- open=1.3421, high=1.3462, low=1.3405, close=1.3452, time=0, Index=0
- )
- cls.bearish_candle = Candle(
- open=1.3452, high=1.3405, low=1.3462, close=1.3421, time=1, Index=1
- )
+ cls.bullish_candle = Candle(open=1.3421, high=1.3462, low=1.3405, close=1.3452, time=0, Index=0)
+ cls.bearish_candle = Candle(open=1.3452, high=1.3405, low=1.3462, close=1.3421, time=1, Index=1)
def test_repr(self):
repr_str = repr(self.bearish_candle)
diff --git a/tests/live/unit/test_get_data.py b/tests/live/unit/test_get_data.py
index 8aed8e8..1adc1b4 100644
--- a/tests/live/unit/test_get_data.py
+++ b/tests/live/unit/test_get_data.py
@@ -14,13 +14,7 @@ class TestGetData:
cls.end = datetime(2024, 2, 2, tzinfo=UTC)
cls.symbols = ["BTCUSD", "ETHUSD"]
cls.timeframes = [TimeFrame.H1, TimeFrame.H2]
- cls.g_data = GetData(
- start=cls.start,
- end=cls.end,
- symbols=cls.symbols,
- timeframes=cls.timeframes,
- name="test_data",
- )
+ cls.g_data = GetData(start=cls.start, end=cls.end, symbols=cls.symbols, timeframes=cls.timeframes, name="test_data")
@pytest.fixture(scope="class", autouse=True)
async def get_data(self):
@@ -34,9 +28,7 @@ class TestGetData:
assert self.g_data.timeframes == set(self.timeframes)
assert self.g_data.name == "test_data"
assert self.g_data.range == range(int((self.end - self.start).total_seconds()))
- assert self.g_data.span == range(
- int(self.start.timestamp()), int(self.end.timestamp())
- )
+ assert self.g_data.span == range(int(self.start.timestamp()), int(self.end.timestamp()))
async def test_get_data(self):
assert self.g_data.data.fully_loaded is True
diff --git a/tests/live/unit/test_meta_trader.py b/tests/live/unit/test_meta_trader.py
index 01becec..1f9fe16 100644
--- a/tests/live/unit/test_meta_trader.py
+++ b/tests/live/unit/test_meta_trader.py
@@ -111,19 +111,13 @@ class TestMetaTrader:
assert res.shape[0] == 10
async def test_copy_ticks_from(self):
- res = await self.mt.copy_ticks_from(
- self.symbol, self.start, 10, self.mt.COPY_TICKS_ALL
- )
+ res = await self.mt.copy_ticks_from(self.symbol, self.start, 10, self.mt.COPY_TICKS_ALL)
assert res is not None
assert res.shape[0] == 10
async def test_copy_ticks_range(self):
- res = await self.mt.copy_ticks_range(
- self.symbol, self.start, self.end, self.mt.COPY_TICKS_ALL
- )
- res2 = self.mt5.copy_ticks_range(
- self.symbol, self.start, self.end, self.mt5.COPY_TICKS_ALL
- )
+ res = await self.mt.copy_ticks_range(self.symbol, self.start, self.end, self.mt.COPY_TICKS_ALL)
+ res2 = self.mt5.copy_ticks_range(self.symbol, self.start, self.end, self.mt5.COPY_TICKS_ALL)
assert res is not None
assert res.shape[0] == res2.shape[0]
@@ -149,9 +143,7 @@ class TestMetaTrader:
price_open = buy_order["price"]
price_close = buy_order["tp"]
type_ = buy_order["type"]
- res = await self.mt.order_calc_profit(
- type_, self.symbol, volume, price_open, price_close
- )
+ res = await self.mt.order_calc_profit(type_, self.symbol, volume, price_open, price_close)
assert isinstance(res, float)
async def test_order_check(self, buy_order):
diff --git a/tests/live/unit/test_result.py b/tests/live/unit/test_result.py
index c1b7be0..747bafc 100644
--- a/tests/live/unit/test_result.py
+++ b/tests/live/unit/test_result.py
@@ -36,9 +36,7 @@ class TestResult:
async def test_json(self, order_results):
res1, res2 = order_results
- await asyncio.gather(
- res1.save(trade_record_mode="json"), res2.save(trade_record_mode="json")
- )
+ await asyncio.gather(res1.save(trade_record_mode="json"), res2.save(trade_record_mode="json"))
assert res1.config.records_dir.exists()
record = res1.config.records_dir / f"{res1.name}.json"
assert record.exists()
diff --git a/tests/live/unit/test_sessions.py b/tests/live/unit/test_sessions.py
index 97ca8cd..4c838bf 100644
--- a/tests/live/unit/test_sessions.py
+++ b/tests/live/unit/test_sessions.py
@@ -15,9 +15,7 @@ class TestSessions:
def make_session(self):
end = time(hour=16, minute=59, second=59, microsecond=999_999, tzinfo=UTC)
london = Session(start=8, end=end, name="London", on_end="close_all")
- start, end = time(hour=0, tzinfo=UTC), time(
- hour=23, minute=59, second=59, tzinfo=UTC
- )
+ start, end = time(hour=0, tzinfo=UTC), time(hour=23, minute=59, second=59, tzinfo=UTC)
all_day = Session(start=start, end=end, name="AllDay", on_end="close_all")
end = time(hour=6, minute=59, second=59, microsecond=999_999, tzinfo=UTC)
over_night = Session(start=18, end=end, name="OverNight", on_end="close_all")
diff --git a/tests/live/unit/test_symbol.py b/tests/live/unit/test_symbol.py
index 7b4eb9e..40a049f 100644
--- a/tests/live/unit/test_symbol.py
+++ b/tests/live/unit/test_symbol.py
@@ -35,19 +35,13 @@ class TestSymbol:
async def test_rates(self, btc):
start = datetime(year=2023, month=10, day=5)
end = start + timedelta(hours=9)
- rates_from = await btc.copy_rates_from(
- timeframe=btc.mt5.TIMEFRAME_H1, date_from=start, count=10
- )
+ rates_from = await btc.copy_rates_from(timeframe=btc.mt5.TIMEFRAME_H1, date_from=start, count=10)
assert isinstance(rates_from, Candles)
assert len(rates_from) == 10
- rates_from_pos = await btc.copy_rates_from_pos(
- timeframe=btc.mt5.TIMEFRAME_H1, count=10, start_position=0
- )
+ rates_from_pos = await btc.copy_rates_from_pos(timeframe=btc.mt5.TIMEFRAME_H1, count=10, start_position=0)
assert isinstance(rates_from_pos, Candles)
assert len(rates_from_pos) == 10
- rates_range = await btc.copy_rates_range(
- timeframe=btc.mt5.TIMEFRAME_H1, date_from=start, date_to=end
- )
+ rates_range = await btc.copy_rates_range(timeframe=btc.mt5.TIMEFRAME_H1, date_from=start, date_to=end)
assert isinstance(rates_range, Candles)
assert len(rates_range) == 10
ticks_from = await btc.copy_ticks_from(date_from=start, count=10)
diff --git a/tests/live/unit/test_trader.py b/tests/live/unit/test_trader.py
index 3fe9907..08f54f7 100644
--- a/tests/live/unit/test_trader.py
+++ b/tests/live/unit/test_trader.py
@@ -27,9 +27,7 @@ class TestTrader:
assert res.retcode == 10009
async def test_create_order_with_sl(self):
- sl = (
- self.trader.symbol.trade_stops_level * 2 + self.trader.symbol.spread
- ) * self.trader.symbol.point
+ sl = (self.trader.symbol.trade_stops_level * 2 + self.trader.symbol.spread) * self.trader.symbol.point
tick = await self.trader.symbol.info_tick()
sl = tick.bid + sl
await self.trader.create_order_with_sl(order_type=OrderType.SELL, sl=sl)
@@ -44,9 +42,7 @@ class TestTrader:
async def test_create_order_with_points(self):
points = self.trader.symbol.trade_stops_level * 2 + self.trader.symbol.spread
- await self.trader.create_order_with_points(
- order_type=OrderType.BUY, points=points
- )
+ await self.trader.create_order_with_points(order_type=OrderType.BUY, points=points)
res = await self.trader.order.send()
profit = floor(await self.trader.order.calc_profit())
loss = -floor(abs(await self.trader.order.calc_loss()))
@@ -57,16 +53,12 @@ class TestTrader:
assert res.retcode == 10009
async def test_create_order_with_stops(self):
- sl = (
- self.trader.symbol.trade_stops_level * 2 + self.trader.symbol.spread
- ) * self.trader.symbol.point
+ sl = (self.trader.symbol.trade_stops_level * 2 + self.trader.symbol.spread) * self.trader.symbol.point
tp = sl * self.trader.ram.risk_to_reward
tick = await self.trader.symbol.info_tick()
sl = tick.ask - sl
tp = tick.ask + tp
- await self.trader.create_order_with_stops(
- order_type=OrderType.BUY, sl=sl, tp=tp
- )
+ await self.trader.create_order_with_stops(order_type=OrderType.BUY, sl=sl, tp=tp)
res = await self.trader.order.send()
profit = floor(await self.trader.order.calc_profit())
loss = -floor(abs(await self.trader.order.calc_loss()))