mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-06 08:47:47 +00:00
v4
This commit is contained in:
@@ -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)
|
||||
|
||||
<a id="aiomql.contrib.symbols.forex_symbol"></a>
|
||||
|
||||
# aiomql.contrib.symbols.forex\_symbol
|
||||
|
||||
<a id="aiomql.contrib.symbols.forex_symbol.ForexSymbol"></a>
|
||||
|
||||
## ForexSymbol Objects
|
||||
|
||||
<a id="forex_symbol.forex_symbol"></a>
|
||||
### 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.
|
||||
|
||||
<a id="aiomql.contrib.symbols.forex_symbol.ForexSymbol.pip"></a>
|
||||
|
||||
#### pip
|
||||
|
||||
<a id="forex_symbol.pip"></a>
|
||||
### 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.
|
||||
|
||||
<a id="aiomql.contrib.symbols.forex_symbol.ForexSymbol.compute_points"></a>
|
||||
|
||||
#### compute\_points
|
||||
#### Returns:
|
||||
|Type|Description|
|
||||
|----|-----------|
|
||||
|float|The pip value of the symbol.|
|
||||
|
||||
<a id="forex_symbol.compute_points"></a>
|
||||
### 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
|
||||
|
||||
<a id="aiomql.contrib.symbols.forex_symbol.ForexSymbol.compute_volume_points"></a>
|
||||
|
||||
#### compute\_volume\_points
|
||||
#### Returns:
|
||||
|Type|Description|
|
||||
|----|-----------|
|
||||
|float|The number of points required for the trade.|
|
||||
|
||||
<a id="forex_symbol.compute_volume_points"></a>
|
||||
### 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.
|
||||
|
||||
|
||||
<a id="forex_symbol.compute_volume_sl"></a>
|
||||
### 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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -36,7 +36,7 @@ The base class for creating strategies.
|
||||
|
||||
|
||||
<a id="strategy.__init__"></a>
|
||||
### \_\_init\_\_
|
||||
### \__init\__
|
||||
```python
|
||||
def __init__(*, symbol: Symbol, params: dict = None, sessions: Sessions, name: str = "")
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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__]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user