diff --git a/docs/contrib/symbols/forex_symbol.md b/docs/contrib/symbols/forex_symbol.md
index dee71a4..b753146 100644
--- a/docs/contrib/symbols/forex_symbol.md
+++ b/docs/contrib/symbols/forex_symbol.md
@@ -1,71 +1,90 @@
-# Table of Contents
+# ForexSymbol
-* [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)
+## Table of Contents
+- [ForexSymbol](#forex_symbol.forex_symbol)
+- [pip](#forex_symbol.pip)
+- [compute_points](#forex_symbol.compute_points)
+- [compute_volume_points](#forex_symbol.compute_volume_points)
+- [compute_volume_sl](#forex_symbol.compute_volume_sl)
-
-
-# aiomql.contrib.symbols.forex\_symbol
-
-
-
-## ForexSymbol Objects
+
+### ForexSymbol
```python
class ForexSymbol(Symbol)
```
+Subclass of Symbol for Forex Symbols. Handles the computation of stop loss, take profit and volume.
-Subclass of Symbol for Forex Symbols. Handles the conversion of currency and the computation of stop loss,
-take profit and volume.
-
-
-
-#### pip
-
+
+### 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
+#### Returns:
+|Type|Description|
+|----|-----------|
+|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**:
+#### Parameters:
+|Name|Type|Description|
+|----|----|-----------|
+|amount|float|Amount to trade|
+|volume|float|Volume to trade|
-- `amount` _float_ - Amount to trade
-- `volume` _float_ - Volume to trade
-
-
-
-#### compute\_volume\_points
+#### Returns:
+|Type|Description|
+|----|-----------|
+|float|The number of points required for the 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**:
+#### Parameters:
+|Name|Type|Description|
+|----|----|-----------|
+|amount|float|Amount to trade|
+|points|float|Number of points|
+|round_down|bool|round down the computed volume to the nearest step default True|
-- `amount` _float_ - Amount to trade
-- `points` _float_ - Number of points
-- `round_down` - round down the computed volume to the nearest step default True
+#### Returns:
+|Type|Description|
+|----|-----------|
+|float|The volume required for the trade.
+
+
+
+### compute_volume_sl
+```python
+async def compute_volume_sl(*, amount: float, price: float, sl: float, round_down: bool = False) -> float
+```
+Compute the volume required for a trade. Given the amount, the price and the stop loss.
+
+#### Parameters:
+|Name|Type|Description|
+|----|----|-----------|
+|amount|float|Amount to trade|
+|price|float|Price of the trade|
+|sl|float|Stop loss|
+|round_down|bool|round down the computed volume to the nearest step default True|
+
+#### Returns:
+|Type|Description|
+|----|-----------|
+|float|The volume required for the trade.
\ No newline at end of file
diff --git a/docs/contrib/traders/scalp_trader.md b/docs/contrib/traders/scalp_trader.md
new file mode 100644
index 0000000..3a4c3fb
Binary files /dev/null and b/docs/contrib/traders/scalp_trader.md differ
diff --git a/docs/contrib/traders/simple_trader.md b/docs/contrib/traders/simple_trader.md
new file mode 100644
index 0000000..c0686fa
Binary files /dev/null and b/docs/contrib/traders/simple_trader.md differ
diff --git a/docs/contrib/utils/tracker.md b/docs/contrib/utils/tracker.md
new file mode 100644
index 0000000..84ed7fc
Binary files /dev/null and b/docs/contrib/utils/tracker.md differ
diff --git a/docs/lib/strategy.md b/docs/lib/strategy.md
index 0e53231..dabdae5 100644
--- a/docs/lib/strategy.md
+++ b/docs/lib/strategy.md
@@ -36,7 +36,7 @@ The base class for creating strategies.
-### \_\_init\_\_
+### \__init\__
```python
def __init__(*, symbol: Symbol, params: dict = None, sessions: Sessions, name: str = "")
```
diff --git a/src/aiomql/contrib/symbols/forex_symbol.py b/src/aiomql/contrib/symbols/forex_symbol.py
index 05a2c5d..b4c2ac7 100644
--- a/src/aiomql/contrib/symbols/forex_symbol.py
+++ b/src/aiomql/contrib/symbols/forex_symbol.py
@@ -17,6 +17,7 @@ class ForexSymbol(Symbol):
def compute_points(self, *, amount: float, volume: float) -> float:
"""Compute the number of points required for a trade. Given the amount and the volume of the trade.
+
Args:
amount (float): Amount to trade
volume (float): Volume to trade
@@ -36,5 +37,16 @@ class ForexSymbol(Symbol):
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:
+ """Compute the volume required for a trade. Given the amount, the price and the stop loss.
+
+ Args:
+ amount (float): Amount to trade
+ price (float): The price of the trade
+ sl (float): The stop loss of the trade
+ round_down (bool): round down the computed volume to the nearest step default to False
+
+ Returns:
+ float: The volume required for the trade
+ """
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 d3a55c1..32f70df 100644
--- a/src/aiomql/contrib/traders/scalp_trader.py
+++ b/src/aiomql/contrib/traders/scalp_trader.py
@@ -8,7 +8,9 @@ logger = getLogger(__name__)
class ScalpTrader(Trader):
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
+ """Places a trade based on the order_type and volume. The volume is optional. If not provided, the minimum volume
+ for the symbol will be used. This trade is placed without a stop_loss or take_profit. The trade is recorded in the
+ trade_record file.
Args:
order_type (OrderType): The order_type
diff --git a/src/aiomql/contrib/traders/simple_trader.py b/src/aiomql/contrib/traders/simple_trader.py
index 0fa2030..d0308f1 100644
--- a/src/aiomql/contrib/traders/simple_trader.py
+++ b/src/aiomql/contrib/traders/simple_trader.py
@@ -8,7 +8,8 @@ logger = getLogger(__name__)
class SimpleTrader(Trader):
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
+ """Places a trade based on the order_type and a given stop_loss. The volume is based on the amount to risk which is
+ calculated using the Risk Assessment Management instance.
Args:
order_type (OrderType): The order_type
diff --git a/src/aiomql/contrib/utils/tracker.py b/src/aiomql/contrib/utils/tracker.py
index 74cf237..9c201a7 100644
--- a/src/aiomql/contrib/utils/tracker.py
+++ b/src/aiomql/contrib/utils/tracker.py
@@ -23,6 +23,7 @@ class Tracker:
tp: float = 0
def update(self, **kwargs):
+ """Updates the tracker with the given kwargs"""
fields = self.__dict__
for key in kwargs:
if key in fields:
diff --git a/src/aiomql/core/backtesting/backtest_account.py b/src/aiomql/core/backtesting/backtest_account.py
index 0ec8098..f6734bc 100644
--- a/src/aiomql/core/backtesting/backtest_account.py
+++ b/src/aiomql/core/backtesting/backtest_account.py
@@ -6,6 +6,7 @@ from ..constants import AccountTradeMode, AccountMarginMode, AccountStopOutMode
@dataclass
class BackTestAccount:
+ """Account data for backtesting"""
login: int = 0
trade_mode: AccountTradeMode = AccountTradeMode.DEMO
leverage: float = 1
@@ -38,13 +39,21 @@ class BackTestAccount:
__match_args__: ClassVar[tuple]
def get_dict(self, exclude: set = None, include: set = None):
+ """Returns a dictionary of the account data. Using the exclude and include arguments, you can filter the data
+
+ Args:
+ exclude (set): A set of keys to exclude
+ include (set): A set of keys to include
+ """
exclude, include = exclude or set(), include or set()
filter_ = include or set(self.__match_args__).difference(exclude)
return {key: value for key, value in self.asdict().items() if key in filter_}
def asdict(self):
+ """Returns a dictionary of the account data"""
res = {key: getattr(self, key) for key in self.__match_args__}
return res
def set_attrs(self, **kwargs):
+ """Se the attributes of the account data to the instance"""
[setattr(self, k, v) for k, v in kwargs.items() if k in self.__match_args__]
diff --git a/src/aiomql/core/backtesting/backtest_controller.py b/src/aiomql/core/backtesting/backtest_controller.py
index 11f6026..995d80c 100644
--- a/src/aiomql/core/backtesting/backtest_controller.py
+++ b/src/aiomql/core/backtesting/backtest_controller.py
@@ -12,17 +12,28 @@ logger = getLogger(__name__)
class BackTestController:
+ """The controller for the backtesting engine.
+ It also act's as a synchronizier for running multiple strategies (tasks) using a threading.Barrier primitive.
+ It handles the updating of open positions and close them when necessary.
+ It handles the iterator for the backtesting engine and handles it movement in time by moving it to the next time step.
+
+ Attributes:
+ _instance (Self): The instance of the controller
+ config (Config): The configuration for the backtesting engine
+ tasks (list[Task]): The tasks that are being run
+ barrier (Barrier): The barrier for synchronizing the tasks
+ """
_instance: Self
- task_tracker: int
config: Config
tasks: list[Task]
+ barrier: Barrier
+
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls)
cls._instance.config = Config()
cls._instance.barrier = Barrier(1)
- cls._instance.task_tracker = 0
cls._instance.tasks = []
return cls._instance
@@ -31,16 +42,25 @@ class BackTestController:
@property
def backtest_engine(self):
+ """Returns the backtest engine"""
return self.config.backtest_engine
def add_tasks(self, *tasks: Task):
+ """Adds tasks to the tasks list"""
self.tasks.extend(tasks)
def set_parties(self, *, parties: int):
+ """Sets the number of parties for the barrier. The barrier will wait for the number of parties to reach the barrier.
+ This has to be done here as it can be impossible to know the eventual number of parties to set the barrier to during initialization.
+
+ Args:
+ parties (int): The number of parties to set the barrier to
+ """
self.barrier._parties = parties
@property
def parties(self):
+ """Returns the number of parties for the barrier"""
return self.barrier.parties
def sigint_handler(self, sig, frame):
@@ -48,14 +68,19 @@ class BackTestController:
self.backtest_engine.stop_testing = True
async def control(self):
+ """The backtest controller. It controls the backtesting engine and the tasks that are being run.
+ It acts as a synchronizer for the tasks and the backtesting engine.
+ """
try:
self.backtest_engine.next()
while True:
pending = self.wait()
- if pending == 0: # all main tasks have been completed in the current cycle
+ # all main tasks have been completed in the current cycle
+ if pending == 0:
await self.backtest_engine.tracker()
self.backtest_engine.next()
- if self.backtest_engine.cursor.time % 3600 == 0:
+ # gives an output every 6 hours
+ if self.backtest_engine.cursor.time % (3600 * 6) == 0:
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(
@@ -74,10 +99,12 @@ class BackTestController:
return
def stop_backtesting(self):
+ """Stop the backtester, and shutdown the executor"""
self.abort()
self.config.shutdown = True
def wait(self):
+ """Called by individual tasks to indicate completion of their cycle"""
try:
pending = self.barrier.wait()
return pending
@@ -87,4 +114,5 @@ class BackTestController:
logger.error("Error: %s in wait", err)
def abort(self):
+ """Aborts the barrier"""
self.barrier.abort()
diff --git a/src/aiomql/core/backtesting/backtest_engine.py b/src/aiomql/core/backtesting/backtest_engine.py
index 7bbfec3..6ee47ff 100644
--- a/src/aiomql/core/backtesting/backtest_engine.py
+++ b/src/aiomql/core/backtesting/backtest_engine.py
@@ -76,6 +76,68 @@ class BackTestEngine:
assign_to_config: bool = True,
account_info: dict = None,
):
+ """The BackTestEngine class is used to simulate trading strategies on historical data.
+ It can accept already saved data or create new data for backtesting on the fly. Ideally only one instance of this class should be created per session.
+ By default it is automatically assigned to the global config instance during instantiation,
+ replacing any existing backtest engine instance. But this is a configurable behaviour.
+ The start and end time can still be specified even when test data is provided. In that case it will be used to set the range of the backtest.
+
+ Args:
+ data (BackTestData, optional): The data to use for backtesting. Defaults to None.
+
+ speed (int, optional): The speed of the backtest. Defaults to 60 seconds.
+
+ start (float | datetime, optional): The start time of the backtest. Defaults to 0. If a float is passed, it is assumed to be a timestamp.
+
+ end (float | datetime, optional): The end time of the backtest. Defaults to 0. If a float is passed, it is assumed to be a timestamp.
+
+ restart (bool, optional): Whether to restart the backtest from the beginning. Defaults to True. This is useful when resuming a backtest
+ using a saved BackTestData instance.
+
+ use_terminal (bool, optional): Whether to use the terminal for backtesting. Defaults to None. If None, it uses the global config setting.
+ If use terminal is true, the backtest engine will use the terminal to get price data, compute margins, profit and check order viability.
+ If false, it will use the data provided in the BackTestData instance and default algorithm for the calculations
+
+ name (str, optional): The name of the backtest. Defaults to "". If not provided, it is generated from the start and end times.
+
+ stop_time (float | datetime, optional): The time to stop the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp.
+ If not given it is asummed to be the end of the backtest range.
+
+ close_open_positions_on_exit (bool, optional): Whether to close all open positions when the backtest is stopped. Defaults to True.
+
+ preload (bool, optional): Whether to preload the ticks for the backtest. Defaults to True.
+
+ assign_to_config (bool, optional): Whether to assign the backtest engine to the global config instance. Defaults to True.
+
+ account_info (dict, optional): A dictionary of account information to use for the backtest. Defaults to None. Use this to set
+ the account information for the backtest.
+
+ Attributes:
+ _data (BackTestData): The data used for backtesting. This is the data that is saved to disk when the backtest is stopped.
+
+ mt5 (MetaTrader): The MetaTrader instance for the backtest engine.
+
+ config (Config): The global configuration instance.
+
+ name (str): The name of the backtest.
+
+ stop_testing (bool): Whether to stop the backtest.
+
+ use_terminal (bool): Whether to use the terminal for backtesting.
+
+ close_open_positions_on_exit (bool): Whether to close all open positions when the backtest is stopped.
+
+ stop_time (int): The time to stop the backtest.
+
+ preload (bool): Whether to preload the ticks for the backtest.
+
+ preloaded_ticks (dict): A dictionary of preloaded ticks for the backtest.
+
+ account_lock (RLock): A reentrant lock for the account data.
+
+ account_info (dict): A dictionary of account information for the backtest.
+
+ """
self._data = data or BackTestData()
self.mt5 = MetaTrader()
self.config = self.mt5.config
@@ -113,6 +175,14 @@ class BackTestEngine:
return f"{self.__class__.__name__}()"
def setup_test_range(self, *, start: float | datetime = None, end: float | datetime = None, speed: int = 60, restart: bool = True):
+ """Setup the test range for the backtest engine. This is used to set the range of the backtest and the speed at which it runs.
+
+ Args:
+ start (float | datetime, optional): The start time of the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp.
+ end (float | datetime, optional): The end time of the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp.
+ speed (int, optional): The speed of the backtest. Defaults to 60.
+ restart (bool, optional): Whether to restart the backtest. Defaults to True. This is useful when resuming a backtest using a saved BackTestData.
+ """
if self._data.span and self._data.range:
start = start or self._data.span[0]
end = end or self._data.span[-1] + speed