minor bug fix

This commit is contained in:
Ichinga Samuel
2023-11-14 03:24:47 +01:00
parent 2462a02be1
commit 0dad24f38e
21 changed files with 893 additions and 309 deletions
+1 -1
View File
@@ -15,7 +15,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
@@ -45,6 +44,7 @@ nosetests.xml
coverage.xml
*,cover
.hypothesis/
scrap.py
# Translations
*.mo
+461 -143
View File
@@ -12,6 +12,8 @@
* [aiomql.bot\_builder](#aiomql.bot_builder)
* [Bot](#aiomql.bot_builder.Bot)
* [initialize](#aiomql.bot_builder.Bot.initialize)
* [add\_function](#aiomql.bot_builder.Bot.add_function)
* [add\_coroutine](#aiomql.bot_builder.Bot.add_coroutine)
* [execute](#aiomql.bot_builder.Bot.execute)
* [start](#aiomql.bot_builder.Bot.start)
* [add\_strategy](#aiomql.bot_builder.Bot.add_strategy)
@@ -107,6 +109,7 @@
* [add\_workers](#aiomql.executor.Executor.add_workers)
* [remove\_workers](#aiomql.executor.Executor.remove_workers)
* [add\_worker](#aiomql.executor.Executor.add_worker)
* [trade](#aiomql.executor.Executor.trade)
* [run](#aiomql.executor.Executor.run)
* [execute](#aiomql.executor.Executor.execute)
* [aiomql.history](#aiomql.history)
@@ -120,14 +123,13 @@
* [aiomql.lib.strategies.finger\_trap](#aiomql.lib.strategies.finger_trap)
* [Entry](#aiomql.lib.strategies.finger_trap.Entry)
* [aiomql.lib.strategies](#aiomql.lib.strategies)
* [aiomql.lib.symbols.crypto\_symbol](#aiomql.lib.symbols.crypto_symbol)
* [CryptoSymbol](#aiomql.lib.symbols.crypto_symbol.CryptoSymbol)
* [compute\_volume](#aiomql.lib.symbols.crypto_symbol.CryptoSymbol.compute_volume)
* [aiomql.lib.symbols.forex\_symbol](#aiomql.lib.symbols.forex_symbol)
* [ForexSymbol](#aiomql.lib.symbols.forex_symbol.ForexSymbol)
* [pip](#aiomql.lib.symbols.forex_symbol.ForexSymbol.pip)
* [compute\_volume](#aiomql.lib.symbols.forex_symbol.ForexSymbol.compute_volume)
* [aiomql.lib.symbols](#aiomql.lib.symbols)
* [aiomql.lib.traders.simple\_deal\_trader](#aiomql.lib.traders.simple_deal_trader)
* [DealTrader](#aiomql.lib.traders.simple_deal_trader.DealTrader)
* [create\_order](#aiomql.lib.traders.simple_deal_trader.DealTrader.create_order)
* [aiomql.lib.traders](#aiomql.lib.traders)
* [aiomql.lib](#aiomql.lib)
* [aiomql.order](#aiomql.order)
@@ -144,12 +146,12 @@
* [\_\_init\_\_](#aiomql.positions.Positions.__init__)
* [positions\_total](#aiomql.positions.Positions.positions_total)
* [positions\_get](#aiomql.positions.Positions.positions_get)
* [close](#aiomql.positions.Positions.close)
* [close\_all](#aiomql.positions.Positions.close_all)
* [aiomql.ram](#aiomql.ram)
* [RAM](#aiomql.ram.RAM)
* [\_\_init\_\_](#aiomql.ram.RAM.__init__)
* [get\_amount](#aiomql.ram.RAM.get_amount)
* [get\_volume](#aiomql.ram.RAM.get_volume)
* [aiomql.records](#aiomql.records)
* [Records](#aiomql.records.Records)
* [\_\_init\_\_](#aiomql.records.Records.__init__)
@@ -163,6 +165,18 @@
* [\_\_init\_\_](#aiomql.result.Result.__init__)
* [to\_csv](#aiomql.result.Result.to_csv)
* [save\_csv](#aiomql.result.Result.save_csv)
* [aiomql.sessions](#aiomql.sessions)
* [delta](#aiomql.sessions.delta)
* [Session](#aiomql.sessions.Session)
* [\_\_init\_\_](#aiomql.sessions.Session.__init__)
* [begin](#aiomql.sessions.Session.begin)
* [close](#aiomql.sessions.Session.close)
* [action](#aiomql.sessions.Session.action)
* [until](#aiomql.sessions.Session.until)
* [Sessions](#aiomql.sessions.Sessions)
* [find](#aiomql.sessions.Sessions.find)
* [find\_next](#aiomql.sessions.Sessions.find_next)
* [check](#aiomql.sessions.Sessions.check)
* [aiomql.strategy](#aiomql.strategy)
* [Strategy](#aiomql.strategy.Strategy)
* [\_\_init\_\_](#aiomql.strategy.Strategy.__init__)
@@ -178,7 +192,10 @@
* [book\_add](#aiomql.symbol.Symbol.book_add)
* [book\_get](#aiomql.symbol.Symbol.book_get)
* [book\_release](#aiomql.symbol.Symbol.book_release)
* [check\_volume](#aiomql.symbol.Symbol.check_volume)
* [round\_off\_volume](#aiomql.symbol.Symbol.round_off_volume)
* [compute\_volume](#aiomql.symbol.Symbol.compute_volume)
* [convert\_currency](#aiomql.symbol.Symbol.convert_currency)
* [currency\_conversion](#aiomql.symbol.Symbol.currency_conversion)
* [copy\_rates\_from](#aiomql.symbol.Symbol.copy_rates_from)
* [copy\_rates\_from\_pos](#aiomql.symbol.Symbol.copy_rates_from_pos)
@@ -205,6 +222,9 @@
* [\_\_init\_\_](#aiomql.trader.Trader.__init__)
* [create\_order](#aiomql.trader.Trader.create_order)
* [set\_order\_limits](#aiomql.trader.Trader.set_order_limits)
* [set\_trade\_stop\_levels](#aiomql.trader.Trader.set_trade_stop_levels)
* [check\_order](#aiomql.trader.Trader.check_order)
* [record\_trade](#aiomql.trader.Trader.record_trade)
* [place\_trade](#aiomql.trader.Trader.place_trade)
* [aiomql.utils](#aiomql.utils)
* [dict\_to\_string](#aiomql.utils.dict_to_string)
@@ -355,7 +375,7 @@ The bot class. Create a bot instance to run your strategies.
- `account` _Account_ - Account Object.
- `executor` - The default thread executor.
- `symbols` _set[Symbols]_ - A set of symbols for the trading session
- `symbols` _list[Symbols]_ - A set of symbols for the trading session
<a id="aiomql.bot_builder.Bot.initialize"></a>
@@ -371,6 +391,37 @@ Prepares the bot by signing in to the trading account and initializing the symbo
SystemExit if sign in was not successful
<a id="aiomql.bot_builder.Bot.add_function"></a>
#### add\_function
```python
def add_function(func: Callable, **kwargs: dict)
```
Add a function to the executor.
**Arguments**:
- `func` _Callable_ - A function to be executed
- `**kwargs` _dict_ - Keyword arguments for the function
<a id="aiomql.bot_builder.Bot.add_coroutine"></a>
#### add\_coroutine
```python
def add_coroutine(coro: Coroutine, **kwargs)
```
Add a coroutine to the executor.
**Arguments**:
- `coro` _Coroutine_ - A coroutine to be executed
- `**kwargs` _dict_ - keyword arguments for the coroutine
<a id="aiomql.bot_builder.Bot.execute"></a>
#### execute
@@ -460,7 +511,7 @@ async def init_symbol(symbol: Symbol) -> Symbol
Initialize a symbol before the beginning of a trading sessions.
Removes it from the list of symbols if it was not successfully initialized or not available
for the current market.
for the account.
**Arguments**:
@@ -2223,6 +2274,8 @@ Executor class for running multiple strategies on multiple symbols concurrently.
- `executor` _ThreadPoolExecutor_ - The executor object.
- `workers` _list_ - List of strategies.
- `coroutines` _dict[Coroutine, dict]_ - A dictionary of coroutines to run in the executor
- `functions` _dict[Callable, dict]_ - A dictionary of functions to run in the executor
<a id="aiomql.executor.Executor.add_workers"></a>
@@ -2243,15 +2296,11 @@ Add multiple strategies at once
#### remove\_workers
```python
def remove_workers(*symbols: Sequence[Symbol])
def remove_workers()
```
Removes any worker running on a symbol not successfully initialized.
**Arguments**:
- `*symbols` - Successfully initialized symbols.
<a id="aiomql.executor.Executor.add_worker"></a>
#### add\_worker
@@ -2266,13 +2315,13 @@ Add a strategy instance to the list of workers
- `strategy` _Strategy_ - A strategy object
<a id="aiomql.executor.Executor.run"></a>
<a id="aiomql.executor.Executor.trade"></a>
#### run
#### trade
```python
@staticmethod
def run(strategy: type(Strategy))
def trade(strategy: type(Strategy))
```
Wraps the coroutine trade method of each strategy with 'asyncio.run'.
@@ -2281,6 +2330,21 @@ Wraps the coroutine trade method of each strategy with 'asyncio.run'.
- `strategy` _Strategy_ - A strategy object
<a id="aiomql.executor.Executor.run"></a>
#### run
```python
def run(func, kwargs: dict)
```
Run a coroutine function
**Arguments**:
- `func` - The coroutine. A variadic function.
- `kwargs` - A dictionary of keyword arguments for the function
<a id="aiomql.executor.Executor.execute"></a>
#### execute
@@ -2333,8 +2397,8 @@ The history class handles completed trade deals and trade orders in the trading
```python
def __init__(*,
date_from: datetime | float = 0,
date_to: datetime | float = 0,
date_from: datetime | float = None,
date_to: datetime | float = None,
group: str = "",
ticket: int = 0,
position: int = 0)
@@ -2441,7 +2505,7 @@ Get total number of orders within the specified period in the constructor.
class Entry()
```
Entry class for FingerTrap strategy.Will be used to store entry conditions and other entry related data.
Entry class for FingerTrap strategy. Will be used to store entry conditions and other entry related data.
**Attributes**:
@@ -2450,15 +2514,53 @@ Entry class for FingerTrap strategy.Will be used to store entry conditions and o
- `ranging` _bool_ - True if the market is ranging
- `snooze` _float_ - Time to wait before checking for entry conditions
- `trend` _str_ - The current trend of the market
- `last_candle` _Candle_ - The last candle of the market
- `new` _bool_ - True if the last candle is new
- `order_type` _OrderType_ - The type of order to place
- `pips` _int_ - The number of pips to place the order from the current price
<a id="aiomql.lib.strategies"></a>
# aiomql.lib.strategies
<a id="aiomql.lib.symbols.crypto_symbol"></a>
# aiomql.lib.symbols.crypto\_symbol
<a id="aiomql.lib.symbols.crypto_symbol.CryptoSymbol"></a>
## CryptoSymbol Objects
```python
class CryptoSymbol(Symbol)
```
Subclass of Symbol for Crypto/Fiat Symbols. Handles the computation of volume based on the amount to risk.
<a id="aiomql.lib.symbols.crypto_symbol.CryptoSymbol.compute_volume"></a>
#### compute\_volume
```python
async def compute_volume(*, amount: float, points, use_limits=False) -> float
```
Compute volume given an amount to risk and target pips. Round the computed volume to the nearest step.
**Arguments**:
- `amount` _float_ - Amount to risk. Given in terms of the account currency.
- `points` _float_ - Target pips.
- `use_limits` _bool_ - If True, the computed volume checked against the maximum and minimum volume.
**Returns**:
- `float` - volume
**Raises**:
- `VolumeError` - If the computed volume is less than the minimum volume or greater than the maximum volume.
<a id="aiomql.lib.symbols.forex_symbol"></a>
# aiomql.lib.symbols.forex\_symbol
@@ -2474,30 +2576,12 @@ class ForexSymbol(Symbol)
Subclass of Symbol for Forex Symbols. Handles the conversion of currency and the computation of stop loss,
take profit and volume.
<a id="aiomql.lib.symbols.forex_symbol.ForexSymbol.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.lib.symbols.forex_symbol.ForexSymbol.compute_volume"></a>
#### compute\_volume
```python
async def compute_volume(*,
amount: float,
pips: float,
use_minimum: bool = True) -> float
async def compute_volume(*, amount: float, pips, use_limits=False) -> float
```
Compute volume given an amount to risk and target pips. Round the computed volume to the nearest step.
@@ -2506,11 +2590,7 @@ Compute volume given an amount to risk and target pips. Round the computed volum
- `amount` _float_ - Amount to risk. Given in terms of the account currency.
- `pips` _float_ - Target pips.
**Arguments**:
- `use_minimum` _bool_ - If True, the minimum volume is returned if the computed volume is less than the minimum volume.
- `use_limits` _bool_ - If True, the computed volume checked against the maximum and minimum volume.
**Returns**:
@@ -2526,36 +2606,6 @@ Compute volume given an amount to risk and target pips. Round the computed volum
# aiomql.lib.symbols
<a id="aiomql.lib.traders.simple_deal_trader"></a>
# aiomql.lib.traders.simple\_deal\_trader
<a id="aiomql.lib.traders.simple_deal_trader.DealTrader"></a>
## DealTrader Objects
```python
class DealTrader(Trader)
```
A base class for placing trades based on the number of pips to target
<a id="aiomql.lib.traders.simple_deal_trader.DealTrader.create_order"></a>
#### create\_order
```python
async def create_order(*, order_type: OrderType, pips: float = 0)
```
Using the number of target pips it determines the lot size, stop loss and take profit for the order,
and updates the order object with the values.
**Arguments**:
- `order_type` _OrderType_ - Type of order
- `pips` _float_ - Target pips
<a id="aiomql.lib.traders"></a>
# aiomql.lib.traders
@@ -2770,25 +2820,50 @@ Get the number of open positions.
#### positions\_get
```python
async def positions_get()
async def positions_get(symbol: str = '', group: str = '', ticket: int = 0)
```
Get open positions with the ability to filter by symbol or ticket.
**Arguments**:
- `symbol` _str_ - Financial instrument name.
- `group` _str_ - The filter for arranging a group of necessary symbols. Optional named parameter. If the group
is specified, the function returns only positions meeting a specified criteria for a symbol name.
- `ticket` _int_ - Position ticket
**Returns**:
- `list[TradePosition]` - A list of open trade positions
<a id="aiomql.positions.Positions.close"></a>
#### close
```python
async def close(*, ticket: int, symbol: str, price: float, volume: float,
order_type: OrderType)
```
Close an open position for the trading account.
<a id="aiomql.positions.Positions.close_all"></a>
#### close\_all
```python
async def close_all() -> int
async def close_all(symbol: str = '', group: str = '') -> int
```
Close all open positions for the trading account.
**Arguments**:
- `symbol` _str_ - Financial instrument name.
- `group` _str_ - The filter for specifying a group of symbols.
**Returns**:
- `int` - Return number of positions closed.
@@ -2812,21 +2887,21 @@ class RAM()
#### \_\_init\_\_
```python
def __init__(**kwargs)
def __init__(*,
risk_to_reward: float = 1,
risk: float = 0.01,
amount: float = 0,
**kwargs)
```
Risk Assessment and Management. All provided keyword arguments are set as attributes.
Initialize Risk Assessment and Management with the provided keyword arguments.
**Arguments**:
- `kwargs` _Dict_ - Keyword arguments.
Defaults:
- `risk_to_reward` _float_ - Risk to reward ratio 1
- `risk_to_reward` _float_ - Risk to reward ratio. Defaults to 1
- `risk` _float_ - Percentage of account balance to risk per trade 0.01 # 1%
- `amount` _float_ - Amount to risk per trade in terms of account currency 0
- `pips` _float_ - Target pips 0
- `volume` _float_ - Volume to trade 0
- `kwargs` - extra keyword arguments are set as object attributes
<a id="aiomql.ram.RAM.get_amount"></a>
@@ -2836,7 +2911,7 @@ Risk Assessment and Management. All provided keyword arguments are set as attrib
async def get_amount(risk: float = 0) -> float
```
Calculate the amount to risk per trade as a percentage of free margin.
Calculate the amount to risk per trade as a percentage of equity.
**Arguments**:
@@ -2847,35 +2922,6 @@ Calculate the amount to risk per trade as a percentage of free margin.
- `float` - Amount to risk per trade
<a id="aiomql.ram.RAM.get_volume"></a>
#### get\_volume
```python
async def get_volume(*,
symbol: Symbol,
pips: float = 0,
amount: float = 0) -> float
```
Calculate the volume to trade. if pips is not provided, the pips attribute is used.
If the amount attribute or amount argument is zero, the amount is calculated using the get_amount method based on the risk.
**Arguments**:
- `symbol` _Symbol_ - Financial instrument
**Arguments**:
- `pips` _float_ - Target pips. Defaults to zero.
- `amount` _float_ - Amount to risk per trade. Defaults to zero.
**Returns**:
- `float` - Volume to trade
<a id="aiomql.records"></a>
# aiomql.records
@@ -2906,7 +2952,8 @@ This utility class read trade records from csv files, and update them based on t
def __init__(records_dir: Path = '')
```
Initialize the Records class.
Initialize the Records class. The main method of this class is update_records which you should call to update
all the records specified in the records_dir.
**Arguments**:
@@ -3020,7 +3067,7 @@ Prepare result data
#### to\_csv
```python
async def to_csv()
def to_csv()
```
Record trade results and associated parameters as a csv file
@@ -3035,6 +3082,201 @@ async def save_csv()
Save trade results and associated parameters as a csv file in a separate thread
<a id="aiomql.sessions"></a>
# aiomql.sessions
Sessions allow you to run code at specific times of the day.
<a id="aiomql.sessions.delta"></a>
#### delta
```python
def delta(obj: time)
```
Get the timedelta of a datetime.time object.
**Arguments**:
- `obj` _datetime.time_ - A datetime.time object.
<a id="aiomql.sessions.Session"></a>
## Session Objects
```python
class Session()
```
A session is a time period between two datetime.time objects specified in utc.
**Attributes**:
- `start` _datetime.time_ - The start time of the session.
- `end` _datetime.time_ - The end time of the session.
- `on_start` _str_ - The action to take when the session starts. Default is None.
- `on_end` _str_ - The action to take when the session ends. Default is None.
- `custom_start` _Callable_ - A custom function to call when the session starts. Default is None.
- `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.
**Methods**:
- `begin` - Call the action specified in on_start or custom_start.
- `close` - Call the action specified in on_end or custom_end.
- `action` - Used by begin and close to call the action specified.
- `delta` - Get the timedelta of a datetime.time object.
- `until` - Get the seconds until the session starts from the current time.
<a id="aiomql.sessions.Session.__init__"></a>
#### \_\_init\_\_
```python
def __init__(*,
start: int | time,
end: int | time,
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,
name: str = '')
```
Create a session.
**Arguments**:
- `start` _int | datetime.time_ - The start time of the session in UTC.
- `end` _int | datetime.time_ - The end time of the session in UTC.
- `on_start` _Literal['close_all', 'close_win', 'close_loss', 'custom_start']_ - The action to take when the
session starts. Default is None.
- `on_end` _Literal['close_all', 'close_win', 'close_loss', 'custom_end']_ - The action to take when the session
ends. Default is None.
- `custom_start` _Callable_ - A custom function to call when the session starts. Default is None.
- `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.
<a id="aiomql.sessions.Session.begin"></a>
#### begin
```python
async def begin()
```
Call the action specified in on_start or custom_start.
<a id="aiomql.sessions.Session.close"></a>
#### close
```python
async def close()
```
Call the action specified in on_end or custom_end.
<a id="aiomql.sessions.Session.action"></a>
#### action
```python
async def action(action)
```
Used by begin and close to call the action specified.
**Arguments**:
- `action` _Literal['close_all', 'close_win', 'close_loss', 'custom_start', 'custom_end']_ - The action to take.
<a id="aiomql.sessions.Session.until"></a>
#### until
```python
def until()
```
Get the seconds until the session starts from the current time in seconds.
<a id="aiomql.sessions.Sessions"></a>
## Sessions Objects
```python
class Sessions()
```
Sessions allow you to run code at specific times of the day. It is a collection of Session objects.
Sessions are sorted by start time. The sessions object is an asynchronous context manager.
**Attributes**:
- `sessions` _list[Session]_ - A list of Session objects.
- `current_session` _Session_ - The current session.
**Methods**:
- `find` - Find a session that contains a datetime.time object.
- `find_next` - Find the next session that contains a datetime.time object.
- `check` - Check if the current session has started and if not, wait until it starts.
<a id="aiomql.sessions.Sessions.find"></a>
#### find
```python
def find(obj: time) -> Session | None
```
Find a session that contains a datetime.time object.
**Arguments**:
- `obj` _datetime.time_ - A datetime.time object.
**Returns**:
Session | None: A Session object or None if not found.
<a id="aiomql.sessions.Sessions.find_next"></a>
#### find\_next
```python
def find_next(obj: time) -> Session
```
Find the next session that contains a datetime.time object.
**Arguments**:
- `obj` _datetime.time_ - A datetime.time object.
**Returns**:
- `Session` - A Session object.
<a id="aiomql.sessions.Sessions.check"></a>
#### check
```python
async def check()
```
Check if the current session has started and if not, wait until it starts.
<a id="aiomql.strategy"></a>
# aiomql.strategy
@@ -3072,7 +3314,10 @@ The base class for creating strategies.
#### \_\_init\_\_
```python
def __init__(*, symbol: Symbol, params: dict = None)
def __init__(*,
symbol: Symbol,
params: dict = None,
sessions: Sessions = None)
```
Initiate the parameters dict and add name and symbol fields.
@@ -3281,36 +3526,75 @@ Cancels subscription of the MetaTrader 5 terminal to the Market Depth change eve
- `bool` - True if successful, otherwise False.
<a id="aiomql.symbol.Symbol.check_volume"></a>
#### check\_volume
```python
def check_volume(volume) -> tuple[bool, float]
```
Check if the volume is within the limits of the symbol. If not, return the nearest limit.
**Arguments**:
- `volume` _float_ - Volume to check
- `Returns` - tuple[bool, float]: Returns a tuple of a boolean and a float. The boolean indicates if the volume is
within the limits of the symbol. The float is the volume to use if the volume is not within the limits of the
symbol.
<a id="aiomql.symbol.Symbol.round_off_volume"></a>
#### round\_off\_volume
```python
def round_off_volume(volume) -> float
```
Round off the volume to the nearest volume step.
**Arguments**:
- `volume` _float_ - Volume to round off
**Returns**:
- `float` - Rounded off volume
<a id="aiomql.symbol.Symbol.compute_volume"></a>
#### compute\_volume
```python
async def compute_volume(*,
amount: float,
pips: float,
use_minimum: bool = True) -> float
async def compute_volume(*args, **kwargs) -> float
```
Computes the volume of a trade based on the amount and the number of pips to target.
Computes the volume required for a trade usually based on the amount and any other keyword arguments.
This is a dummy method that returns the minimum volume of the symbol. It is meant to be overridden by a subclass
Checkout Forex Symbol implementation in srciomql\lib\ForexSymbol.py
that implements the computation of volume.
**Arguments**:
- `amount` _float_ - Amount to risk in the trade
- `pips` _float_ - Number of pips to target
**Arguments**:
- `use_minimum` _bool_ - If True, the minimum volume is returned if the computed volume is less than the minimum volume.
- `use_limits` _bool_ - round up or round down the computed volume to the nearest volume limit i.e volume_min
or volume_max
**Returns**:
- `float` - Returns the volume of the trade
<a id="aiomql.symbol.Symbol.convert_currency"></a>
#### convert\_currency
```python
async def convert_currency(*, amount: float, base: str, quote: str) -> float
```
Convert from one currency to the other. Alias for currency_conversion
<a id="aiomql.symbol.Symbol.currency_conversion"></a>
#### currency\_conversion
@@ -3331,7 +3615,7 @@ Convert from one currency to the other.
**Returns**:
- `float` - Amount in terms of the base currency or None if it failed to convert
- `float` - Amount in terms of the base currency
**Raises**:
@@ -3351,15 +3635,13 @@ async def copy_rates_from(*,
Get bars from the MetaTrader 5 terminal starting from the specified date.
**Arguments**:
Args: timeframe (TimeFrame): Timeframe the bars are requested for. Set by a value from the TimeFrame
enumeration. Required unnamed parameter.
- `timeframe` _TimeFrame_ - Timeframe the bars are requested for. Set by a value from the TimeFrame enumeration. Required unnamed parameter.
- `date_from` _datetime | int_ - Date of opening of the first bar from the requested sample. Set by the 'datetime' object or as a number
of seconds elapsed since 1970.01.01. Required unnamed parameter.
- `count` _int_ - Number of bars to receive. Required unnamed parameter.
date_from (datetime | int): Date of opening of the first bar from the requested sample. Set by the
'datetime' object or as a number of seconds elapsed since 1970.01.01. Required unnamed parameter.
count (int): Number of bars to receive. Required unnamed parameter.
**Returns**:
@@ -3776,7 +4058,6 @@ async def create_order(*, order_type: OrderType, **kwargs)
```
Complete the order object with the required values. Creates a simple order.
Uses the ram instance to set the volume.
**Arguments**:
@@ -3791,13 +4072,50 @@ Uses the ram instance to set the volume.
async def set_order_limits(pips: float)
```
Sets the stop loss and take profit for the order.
This method uses pips as defined for forex instruments.
Sets the stop loss and take profit for the order. This method uses pips as defined for forex instruments.
**Arguments**:
- `pips` - Target pips
<a id="aiomql.trader.Trader.set_trade_stop_levels"></a>
#### set\_trade\_stop\_levels
```python
async def set_trade_stop_levels(*, points)
```
Set the stop loss and take profit levels of the order based on the points.
<a id="aiomql.trader.Trader.check_order"></a>
#### check\_order
```python
async def check_order() -> bool
```
Check order before sending it to the broker.
**Returns**:
- `bool` - True if order can go through else false
<a id="aiomql.trader.Trader.record_trade"></a>
#### record\_trade
```python
async def record_trade(result: OrderSendResult)
```
Record the trade in a csv file.
**Arguments**:
- `result` _OrderSendResult_ - Result of the order send
<a id="aiomql.trader.Trader.place_trade"></a>
#### place\_trade
@@ -3811,7 +4129,7 @@ Places a trade based on the order_type.
**Arguments**:
- `order_type` _OrderType_ - Type of order
- `params` - parameters to be saved with the trade
- `params` - parameters of the trading strategy used to place the trade
- `kwargs` - keyword arguments as required for the specific trader
<a id="aiomql.utils"></a>
+18 -62
View File
@@ -1,83 +1,39 @@
<a id="aiomql.ram"></a>
# aiomql.ram
Risk Assessment and Management
<a id="aiomql.ram.RAM"></a>
## RAM Objects
## <a id="ram"></a> Risk Assessment and Management
```python
class RAM()
```
<a id="aiomql.ram.RAM.__init__"></a>
#### \_\_init\_\_
### \_\_init\_\_
```python
def __init__(**kwargs)
```
Risk Assessment and Management. All provided keyword arguments are set as attributes.
**Arguments**:
#### Parameters
| Name | Type | Description | Default |
|----------------|------|-------------------------------------------------------|---------|
| risk_to_reward | float | Risk to reward ratio | 1|
| risk | float | Percentage of account balance to risk per trade | 0.01 # 1%|
| amount | float | Amount to risk per trade in terms of account currency | 0|
| **kwargs** | Dict | Keyword arguments to be set as object attributes | {} |
- `kwargs` _Dict_ - Keyword arguments.
Defaults:
- `risk_to_reward` _float_ - Risk to reward ratio 1
- `risk` _float_ - Percentage of account balance to risk per trade 0.01 # 1%
- `amount` _float_ - Amount to risk per trade in terms of account currency 0
- `pips` _float_ - Target pips 0
- `volume` _float_ - Volume to trade 0
<a id="aiomql.ram.RAM.get_amount"></a>
#### get\_amount
### get\_amount
```python
async def get_amount(risk: float = 0) -> float
```
Calculate the amount to risk per trade as a percentage of free margin.
**Arguments**:
- `risk` _float_ - Percentage of account balance to risk per trade. Defaults to zero.
**Returns**:
- `float` - Amount to risk per trade
<a id="aiomql.ram.RAM.get_volume"></a>
#### get\_volume
```python
async def get_volume(*,
symbol: Symbol,
pips: float = 0,
amount: float = 0) -> float
```
Calculate the volume to trade. if pips is not provided, the pips attribute is used.
If the amount attribute or amount argument is zero, the amount is calculated using the get_amount method based on the risk.
**Arguments**:
- `symbol` _Symbol_ - Financial instrument
**Arguments**:
- `pips` _float_ - Target pips. Defaults to zero.
- `amount` _float_ - Amount to risk per trade. Defaults to zero.
**Returns**:
- `float` - Volume to trade
#### Parameters
| Name | Type | Description | Default |
|----------------|------|-------------------------------------------------------|---------|
| risk | float | Percentage of account balance to risk per trade | 0.01 # 1%|
#### Returns
| Name | Type | Description |
|----------------|------|-------------------------------------------------------|
| amount | float | Amount to risk per trade in terms of account currency |
-1
View File
@@ -17,7 +17,6 @@ A session is a time period between two datetime.time objects specified in utc.
|**custom_start**| **Callable** | A custom function to call when the session starts. Default is None. | None |
|**custom_end**| **Callable** | A custom function to call when the session ends. Default is None. | None |
|**name**| **str** | The name of the session. Default is a combination of start and finish. | |
|**seconds**| **set[int]** | The set of seconds in the session. | None |
### Methods:
|Name|Description|
+17 -6
View File
@@ -295,7 +295,7 @@ async def copy_ticks_range(*,
flags: CopyTicks = CopyTicks.ALL) -> Ticks
```
Get ticks for the specified date range from the MetaTrader 5 terminal.
#### Arguments:
#### Parameters:
|Name| Type | Description | Default |
|---|--------------------|-----------------------------|-------------------|
|**date_from**| **datetime, int** | Date the ticks are requested from. Set by the 'datetime' object or as a number of seconds elapsed since 1970.01.01. | Required unnamed parameter |
@@ -310,13 +310,24 @@ Get ticks for the specified date range from the MetaTrader 5 terminal.
|---|---|
|**ValueError**|If request was unsuccessful and None was returned|
### check_volume
```python
async def check_volume(*, volume: float) -> tuple[bool, float]
```
Checks if the volume is within the minimum and maximum volume for the symbol. If not, return the nearest limit.
### round_off_volume
```python
async def round_off_volume(*, volume: float) -> float
```
Rounds off the volume to the nearest minimum or maximum volume for the symbol.
### compute_volume
```python
async def compute_volume(*,
amount: float,
pips: float,
use_limits: bool = True) -> float
async def compute_volume(*args, **kwargs) -> float
```
Computes the volume of a trade based on the amount and the number of pips to target.
Computes the volume of a trade based on the amount and other parameters.
This is a dummy method that returns the minimum volume of the symbol. It is meant to be overridden by a subclass
Checkout Forex Symbol implementation in [ForexSymbol](#forexsymbol)
+2 -1
View File
@@ -22,7 +22,8 @@ def build_bot():
sess = Session(name='London', start=8, end=time(hour=15, minute=30), on_end='close_all')
sess2 = Session(name='New York', start=13, end=time(hour=20, minute=30))
sess3 = Session(name='Tokyo', start=23, end=time(hour=6, minute=30))
sessions = Sessions(sess, sess2, sess3)
allsess = Session(name='All', start=0, end=23, on_end='close_all')
sessions = Sessions(sess, sess2, sess3, allsess)
# configurable parameters for the strategy
params = {'trend_candles_count': 500, 'fast_period': 8}
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "aiomql"
version = "3.0.6"
version = "3.0.7"
readme = "README.md"
requires-python = ">=3.10"
classifiers = [
+2
View File
@@ -11,6 +11,7 @@ from .core.constants import TimeFrame
logger = getLogger(__name__)
class Candle:
"""A class representing bars from the MetaTrader 5 terminal as a customized class analogous to Japanese Candlesticks.
You can subclass this class for added customization.
@@ -45,6 +46,7 @@ class Candle:
self.time = kwargs.pop('time', 0)
self.Index = kwargs.pop('Index', 0)
self.set_attributes(**kwargs)
def __repr__(self):
keys = reprlib.repr(', '.join('%s=%s' % (i, j) for i, j in self.__dict__.items()))[1:-1]
return '%(class)s(%(args)s)' % {'class': self.__class__.__name__, 'args': keys}
+3
View File
@@ -0,0 +1,3 @@
from .strategies import *
from .traders import *
from .symbols import *
+1
View File
@@ -0,0 +1 @@
from .finger_trap import FingerTrap
+210
View File
@@ -0,0 +1,210 @@
import asyncio
import logging
from typing import Literal
from dataclasses import dataclass
from ...symbol import Symbol
from ...trader import Trader
from ...candle import Candles
from ...strategy import Strategy
from ...core import TimeFrame, OrderType
from ...sessions import Sessions
logger = logging.getLogger(__name__)
@dataclass
class Entry:
"""
Entry class for FingerTrap strategy. Will be used to store entry conditions and other entry related data.
Attributes:
bearish (bool): True if the market is bearish
bullish (bool): True if the market is bullish
ranging (bool): True if the market is ranging
snooze (float): Time to wait before checking for entry conditions
trend (str): The current trend of the market
new (bool): True if the last candle is new
order_type (OrderType): The type of order to place
"""
bearish: bool = False
bullish: bool = False
ranging: bool = True
trending: bool = False
trend: Literal["ranging", "bullish", "bearish"] = "ranging"
snooze: float = 0
last_trend_time: float = 0
last_entry_time: float = 0
new: bool = True
order_type: OrderType | None = None
def update(self, **kwargs):
fields = self.__dict__
for key in kwargs:
if key in fields:
setattr(self, key, kwargs[key])
match self.trend:
case "ranging":
self.ranging = True
self.trending = self.bullish = self.bearish = False
case "bullish":
self.ranging = self.bearish = False
self.bullish = self.trending = True
case "bearish":
self.ranging = self.bullish = False
self.bearish = self.trending = True
class FingerTrap(Strategy):
trend_time_frame: TimeFrame
entry_time_frame: TimeFrame
trend: int
fast_period: int
slow_period: int
entry_period: int
parameters: dict
prices: Candles
name = "FingerTrap"
interval: TimeFrame
entry_candles_count: int
trend_candles_count: int
def __init__(
self,
*,
symbol: Symbol,
params: dict | None = None,
trader: Trader = None,
sessions: Sessions = None,
):
super().__init__(symbol=symbol, params=params, sessions=sessions)
self.trend = self.parameters.get("trend", 3)
self.fast_period = self.parameters.setdefault("fast_period", 8)
self.slow_period = self.parameters.setdefault("slow_period", 34)
self.entry_time_frame = self.parameters.setdefault(
"entry_time_frame", TimeFrame.M5
)
self.trend_time_frame = self.parameters.setdefault(
"trend_time_frame", TimeFrame.H1
)
self.trader = trader or Trader(symbol=self.symbol)
self.entry: Entry = Entry(snooze=self.trend_time_frame.time)
self.entry_period = self.parameters.setdefault("entry_period", 8)
self.trend_candles_count = self.parameters.setdefault(
"trend_candles_count", 86400 // self.trend_time_frame.time
)
self.trend_candles_count = max(self.trend_candles_count, self.slow_period)
self.entry_candles_count = self.trend_candles_count * (
self.trend_time_frame.time // self.entry_time_frame.time
)
self.entry_candles_count = max(self.entry_candles_count, self.entry_period)
async def check_trend(self):
try:
candles = await self.symbol.copy_rates_from_pos(
timeframe=self.trend_time_frame, count=self.trend_candles_count
)
current = candles[-1]
if current.time > self.entry.last_trend_time:
self.entry.update(new=True, last_trend_time=current.time)
else:
self.entry.update(new=False)
return
candles.ta.ema(length=self.slow_period, append=True, fillna=0)
candles.ta.ema(length=self.fast_period, append=True, fillna=0)
candles.rename(
inplace=True,
**{
f"EMA_{self.fast_period}": "fast",
f"EMA_{self.slow_period}": "slow",
},
)
# Compute
candles["fast_A_slow"] = candles.ta_lib.above(candles.fast, candles.slow)
candles["fast_B_slow"] = candles.ta_lib.below(candles.fast, candles.slow)
candles["close_A_fast"] = candles.ta_lib.above(candles.close, candles.fast)
candles["close_B_fast"] = candles.ta_lib.below(candles.close, candles.fast)
trend = candles[-self.trend : -1]
if all(
(c.is_bullish() and c.fast_A_slow and c.close_A_fast) for c in trend
):
self.entry.update(trend="bullish")
elif all(
c.is_bearish() and c.fast_B_slow and c.close_B_fast for c in trend
):
self.entry.update(trend="bearish")
else:
self.entry.update(trend="ranging", snooze=self.trend_time_frame.time)
except Exception as exe:
logger.error(f"{exe}. Error in {self.__class__.__name__}.check_trend")
async def confirm_trend(self):
try:
candles = await self.symbol.copy_rates_from_pos(
timeframe=self.entry_time_frame, count=self.entry_candles_count
)
current = candles[-1]
if current.time > self.entry.last_entry_time:
self.entry.update(new=True, last_entry_time=current.time)
else:
self.entry.update(new=False)
return
candles.ta.ema(length=self.entry_period, append=True, fillna=0)
candles.rename(**{f"EMA_{self.entry_period}": "ema"})
candles["close_A_ema"] = candles.ta_lib.above(candles.close, candles.ema)
candles["close_B_ema"] = candles.ta_lib.below(candles.close, candles.ema)
candles["close_XA_ema"] = candles.ta_lib.cross(candles.close, candles.ema)
candles["close_XB_ema"] = candles.ta_lib.cross(
candles.close, candles.ema, above=False
)
if self.entry.bullish and current.close_XA_ema:
self.entry.update(
snooze=self.entry_time_frame.time, order_type=OrderType.BUY
)
elif self.entry.bearish and current.close_XB_ema:
self.entry.update(
snooze=self.entry_time_frame.time, order_type=OrderType.SELL
)
else:
self.entry.update(snooze=self.entry_time_frame.time, order_type=None)
except Exception as exe:
logger.error(f"{exe} Error in {self.__class__.__name__}.confirm_trend")
async def watch_market(self):
await self.check_trend()
if not self.entry.ranging:
await self.confirm_trend()
async def trade(self):
logger.info(f"Trading {self.symbol}")
async with self.sessions as sess:
while True:
await sess.check()
try:
await self.watch_market()
if not self.entry.new:
await asyncio.sleep(2)
continue
if self.entry.order_type is None:
await self.sleep(self.entry.snooze)
continue
await self.trader.place_trade(
order_type=self.entry.order_type, params=self.parameters
)
await self.sleep(self.entry.snooze)
except Exception as err:
logger.error(
f"Error: {err}\t Symbol: {self.symbol} in {self.__class__.__name__}.trade"
)
await self.sleep(self.trend_time_frame.time)
continue
+1
View File
@@ -0,0 +1 @@
from .forex_symbol import ForexSymbol
+30
View File
@@ -0,0 +1,30 @@
from ...symbol import Symbol
from ...core.exceptions import VolumeError
class CryptoSymbol(Symbol):
"""Subclass of Symbol for Crypto/Fiat Symbols. Handles the computation of volume based on the amount to risk."""
async def compute_volume(self, *, amount: float, points, use_limits=False) -> float:
"""Compute volume given an amount to risk and target pips. Round the computed volume to the nearest step.
Args:
amount (float): Amount to risk. Given in terms of the account currency.
points (float): Target pips.
use_limits (bool): If True, the computed volume checked against the maximum and minimum volume.
Returns:
float: volume
Raises:
VolumeError: If the computed volume is less than the minimum volume or greater than the maximum volume.
"""
if self.currency_profit != self.account.currency:
amount = await self.convert_currency(amount=amount, base=self.currency_profit, quote=self.account.currency)
volume = amount / (self.point * points * self.trade_contract_size)
volume = self.round_off_volume(volume)
if self.check_volume(volume)[0]:
return volume
if use_limits:
return self.check_volume(volume)[1]
raise VolumeError(f'Incorrect Volume. Computed Volume outside the range of permitted volumes')
+32
View File
@@ -0,0 +1,32 @@
from ...symbol import Symbol
from ...core.exceptions import VolumeError
class ForexSymbol(Symbol):
"""Subclass of Symbol for Forex Symbols. Handles the conversion of currency and the computation of stop loss,
take profit and volume.
"""
async def compute_volume(self, *, amount: float, pips, use_limits=False) -> float:
"""Compute volume given an amount to risk and target pips. Round the computed volume to the nearest step.
Args:
amount (float): Amount to risk. Given in terms of the account currency.
pips (float): Target pips.
use_limits (bool): If True, the computed volume checked against the maximum and minimum volume.
Returns:
float: volume
Raises:
VolumeError: If the computed volume is less than the minimum volume or greater than the maximum volume.
"""
if self.currency_profit != self.account.currency:
amount = await self.convert_currency(amount=amount, base=self.currency_profit, quote=self.account.currency)
volume = amount / (self.pip * pips * self.trade_contract_size)
volume = self.round_off_volume(volume)
if self.check_volume(volume)[0]:
return volume
if use_limits:
return self.check_volume(volume)[1]
raise VolumeError(f'Incorrect Volume. Computed Volume outside the range of permitted volumes')
View File
+3 -25
View File
@@ -1,6 +1,5 @@
"""Risk Assessment and Management"""
from .account import Account
from .symbol import Symbol
class RAM:
@@ -8,24 +7,20 @@ class RAM:
risk_to_reward: float
risk: float
amount: float
pips: float
volume: float
def __init__(self, *, risk_to_reward: float = 1, risk: float = 0.01, amount: float = 0, pips: float = 0, volume=0):
def __init__(self, *, risk_to_reward: float = 1, risk: float = 0.01, amount: float = 0, **kwargs):
"""Initialize Risk Assessment and Management with the provided keyword arguments.
Keyword Args:
risk_to_reward (float): Risk to reward ratio. Defaults to 1
risk (float): Percentage of account balance to risk per trade 0.01 # 1%
amount (float): Amount to risk per trade in terms of account currency 0
pips (float): Target pips to risk
volume (float): Volume to trade 0
kwargs: extra keyword arguments are set as object attributes
"""
self.risk_to_reward = risk_to_reward
self.risk = risk
self.amount = amount
self.pips = pips
self.volume = volume
[setattr(self, key, value) for key, value in kwargs.items()]
async def get_amount(self, risk: float = 0) -> float:
"""Calculate the amount to risk per trade as a percentage of equity.
@@ -39,20 +34,3 @@ class RAM:
await self.account.refresh()
risk = risk or self.risk
return self.account.equity * risk
async def get_volume(self, *, symbol: Symbol, pips: float = 0, amount: float = 0) -> float:
"""Calculate the volume to trade. if pips is not provided, the pips attribute is used.
If the amount attribute or amount argument is zero, the amount is calculated using the get_amount method based
on the risk.
Keyword Args:
symbol (Symbol): Financial instrument
pips (float): Target pips. Defaults to zero.
amount (float): Amount to risk per trade. Defaults to zero.
Returns:
float: Volume to trade
"""
pips = pips or self.pips
amount = amount or self.amount or await self.get_amount()
return await symbol.compute_volume(amount=amount, pips=pips)
+5 -9
View File
@@ -19,12 +19,6 @@ def delta(obj: time):
return timedelta(hours=obj.hour, minutes=obj.minute, seconds=obj.second, microseconds=obj.microsecond)
def seconds(start: time, end: time) -> set[int]:
if start > end:
return set(range(delta(start).seconds, 86400)) | set(range(0, delta(end).seconds))
return set(range(delta(start).seconds, delta(end).seconds))
class Session:
"""A session is a time period between two datetime.time objects specified in utc.
@@ -36,7 +30,6 @@ class Session:
custom_start (Callable): A custom function to call when the session starts. Default is None.
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.
seconds (set[int]): A set of seconds between start and end.
Methods:
begin: Call the action specified in on_start or custom_start.
@@ -69,10 +62,13 @@ class Session:
self.custom_start = custom_start
self.custom_end = custom_end
self.name = name or f'{self.start} - {self.end}'
self.seconds = seconds(self.start, self.end)
def __contains__(self, item: time):
return delta(item).seconds in self.seconds
if self.start > self.end:
m1 = time(hour=23, minute=59, second=59, microsecond=9999)
m2 = time(hour=0)
return self.start <= item <= m1 or m2 <= item < self.end
return self.start <= item < self.end
def __str__(self):
return f'{self.start}-->{self.name}-->{self.end}' if self.name else f'{self.start}-->{self.end}'
+41 -19
View File
@@ -79,7 +79,7 @@ class Symbol(SymbolInfo):
Raises:
ValueError: If request was unsuccessful and None was returned
"""
info = await self.mt5.symbol_info(self.name)
if info:
self.set_attributes(**info._asdict())
@@ -96,6 +96,7 @@ class Symbol(SymbolInfo):
if await self.symbol_select():
await self.book_add()
await self.info()
await self.info_tick()
return True
logger.warning(f'Unable to initialized symbol {self}')
return False
@@ -137,6 +138,15 @@ class Symbol(SymbolInfo):
return await self.mt5.market_book_release(self.name)
def check_volume(self, volume) -> tuple[bool, float]:
"""Check if the volume is within the limits of the symbol. If not, return the nearest limit.
Args:
volume (float): Volume to check
Returns: tuple[bool, float]: Returns a tuple of a boolean and a float. The boolean indicates if the volume is
within the limits of the symbol. The float is the volume to use if the volume is not within the limits of the
symbol.
"""
check = self.volume_min <= volume <= self.volume_max
if check:
return check, volume
@@ -145,25 +155,36 @@ class Symbol(SymbolInfo):
else:
return check, self.volume_max
def round_off_volume(self, volume):
def round_off_volume(self, volume) -> float:
"""Round off the volume to the nearest volume step.
Args:
volume (float): Volume to round off
Returns:
float: Rounded off volume
"""
step = ceil(abs(log10(self.volume_step)))
return round(volume, step)
async def compute_volume(self, *, amount: float, pips: float, use_limits: bool = False) -> float:
"""Computes the volume of a trade based on the amount and the number of pips to target.
async def compute_volume(self, *args, **kwargs) -> float:
"""Computes the volume required for a trade usually based on the amount and any other keyword arguments.
This is a dummy method that returns the minimum volume of the symbol. It is meant to be overridden by a subclass
that implements the computation of volume.
Args:
amount (float): Amount to risk in the trade
pips (float): Number of pips to target
use_limits (bool): If True, the computed volume is rounded to the nearest step and checked against
Keyword Args:
use_limits (bool): round up or round down the computed volume to the nearest volume limit i.e volume_min
or volume_max
Returns:
float: Returns the volume of the trade
"""
return self.volume_min
async def convert_currency(self, *, amount: float, base: str, quote: str) -> float:
"""Convert from one currency to the other. Alias for currency_conversion"""
return await self.currency_conversion(amount=amount, base=base, quote=quote)
async def currency_conversion(self, *, amount: float, base: str, quote: str) -> float:
"""Convert from one currency to the other.
@@ -173,7 +194,7 @@ class Symbol(SymbolInfo):
quote: The quote currency of the pair
Returns:
float: Amount in terms of the base currency or None if it failed to convert
float: Amount in terms of the base currency
Raises:
ValueError: If conversion is impossible
@@ -189,8 +210,7 @@ class Symbol(SymbolInfo):
if self.account.has_symbol(pair):
tick = await self.info_tick(name=pair)
if tick is not None:
amount = amount * tick.bid
return amount
return amount * tick.bid
except Exception as err:
logger.warning(f'Currency conversion failed: Unable to convert {amount} in {quote} to {base}')
raise ValueError(f'Currency Conversion Failed: {err}')
@@ -201,11 +221,11 @@ class Symbol(SymbolInfo):
"""
Get bars from the MetaTrader 5 terminal starting from the specified date.
Args:
timeframe (TimeFrame): Timeframe the bars are requested for. Set by a value from the TimeFrame enumeration. Required unnamed parameter.
Args: timeframe (TimeFrame): Timeframe the bars are requested for. Set by a value from the TimeFrame
enumeration. Required unnamed parameter.
date_from (datetime | int): Date of opening of the first bar from the requested sample. Set by the 'datetime' object or as a number
of seconds elapsed since 1970.01.01. Required unnamed parameter.
date_from (datetime | int): Date of opening of the first bar from the requested sample. Set by the
'datetime' object or as a number of seconds elapsed since 1970.01.01. Required unnamed parameter.
count (int): Number of bars to receive. Required unnamed parameter.
@@ -220,7 +240,7 @@ class Symbol(SymbolInfo):
return Candles(data=rates)
raise ValueError(f'Could not get rates for {self.name}')
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:
@@ -267,7 +287,8 @@ class Symbol(SymbolInfo):
return Candles(data=rates)
raise ValueError(f'Could not get rates for {self.name}')
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.
@@ -289,7 +310,8 @@ class Symbol(SymbolInfo):
return Ticks(data=ticks)
raise ValueError(f'Could not get ticks for {self.name}')
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:
@@ -298,7 +320,7 @@ class Symbol(SymbolInfo):
date_to: Date, up to which the bars are requested. Set by the 'datetime' object or as a number of seconds elapsed since 1970.01.01. Bars
with the open time <= date_to are returned. Required unnamed parameter.
flags (CopyTicks):
Returns:
+63 -40
View File
@@ -8,7 +8,7 @@ from zoneinfo import ZoneInfo
from .order import Order
from .symbol import Symbol as _Symbol
from .ram import RAM
from .core.models import OrderType
from .core.models import OrderType, OrderSendResult
from .core.config import Config
from .utils import dict_to_string
from .result import Result
@@ -43,34 +43,43 @@ class Trader:
self.symbol = symbol
self.order = Order(symbol=symbol.name)
self.ram = ram or RAM()
self.params = {}
async def create_order(self, *, order_type: OrderType, **kwargs):
"""Complete the order object with the required values. Creates a simple order.
Uses the ram instance to set the volume.
Args:
order_type (OrderType): Type of order
kwargs: keyword arguments as required for the specific trader
"""
# check if pips is passed in as a keyword argument, if not use the pips attribute of the ram instance
pips = kwargs.get('pips', 0) or self.ram.pips
self.order.volume = kwargs.get('volume', self.ram.volume) or await self.ram.get_volume(symbol=self.symbol,
pips=pips)
points = kwargs.get('points', self.symbol.trade_stops_level+self.symbol.spread)
self.order.volume = await self.symbol.compute_volume()
self.order.type = order_type
await self.set_order_limits(pips=pips)
await self.set_trade_stop_levels(points=points)
async def set_order_limits(self, pips: float):
"""Sets the stop loss and take profit for the order.
This method uses pips as defined for forex instruments.
"""Sets the stop loss and take profit for the order. This method uses pips as defined for forex instruments.
Args:
pips: Target pips
"""
# use passed in pips and the pip value of the symbol to calculate the stop loss and take profit.
# this is sure to work for forex instruments.
pips = pips * self.symbol.pip
sl, tp = pips, pips * self.ram.risk_to_reward
tick = await self.symbol.info_tick()
if self.order.type == OrderType.BUY:
self.order.sl, self.order.tp = tick.ask - sl, tick.ask + tp
self.order.price = tick.ask
elif self.order.type == OrderType.SELL:
self.order.sl, self.order.tp = tick.bid + sl, tick.bid - tp
self.order.price = tick.bid
else:
raise ValueError(f"Invalid order type: {self.order.type}")
async def set_trade_stop_levels(self, *, points):
"""Set the stop loss and take profit levels of the order based on the points."""
points = points * self.symbol.point
sl, tp = points, points * self.ram.risk_to_reward
tick = await self.symbol.info_tick()
if self.order.type == OrderType.BUY:
self.order.sl, self.order.tp = tick.ask - sl, tick.ask + tp
self.order.price = tick.ask
@@ -78,6 +87,46 @@ class Trader:
self.order.sl, self.order.tp = tick.bid + sl, tick.bid - tp
self.order.price = tick.bid
async def check_order(self) -> bool:
"""Check order before sending it to the broker.
Returns:
bool: True if order can go through else false
"""
check = await self.order.check()
if check.retcode != 0:
logger.warning(
f"Symbol: {self.order.symbol}\nResult:\n{dict_to_string(check.get_dict(include={'comment', 'retcode'}), multi=True)}")
return False
return True
async def send_order(self):
result = await self.order.send()
if result.retcode != 10009:
logger.warning(
f"Symbol: {self.order.symbol}\nResult:\n{dict_to_string(result.get_dict(include={'comment', 'retcode'}), multi=True)}")
return
logger.info(f"Symbol: {self.order.symbol}\nOrder: {dict_to_string(result.dict, multi=True)}\n")
await self.record_trade(result)
async def record_trade(self, result: OrderSendResult):
"""
Record the trade in a csv file.
Args:
result (OrderSendResult): Result of the order send
"""
if result.retcode != 10009 or not self.config.record_trades:
return
profit = await self.order.calc_profit()
params = self.params
params['expected_profit'] = profit
date = datetime.utcnow()
date = date.replace(tzinfo=ZoneInfo('UTC'))
params['date'] = date
params['time'] = date.timestamp()
res = Result(result=result, parameters=params)
await res.save_csv()
async def place_trade(self, order_type: OrderType, params: dict = None, **kwargs):
"""Places a trade based on the order_type.
@@ -88,35 +137,9 @@ class Trader:
"""
try:
await self.create_order(order_type=order_type, **kwargs)
# Check the order before placing it
check = await self.order.check()
if check.retcode != 0:
logger.warning(
f"Symbol: {self.order.symbol}\nResult:\n{dict_to_string(check.get_dict(include={'comment', 'retcode'}), multi=True)}")
if not await self.check_order():
return
# check expected profit
profit = await self.order.calc_profit()
# Send the order.
result = await self.order.send()
if result.retcode != 10009:
logger.warning(
f"Symbol: {self.order.symbol}\nResult:\n{dict_to_string(result.get_dict(include={'comment', 'retcode'}), multi=True)}")
return
logger.info(f"Symbol: {self.order.symbol}\nOrder: {dict_to_string(result.dict, multi=True)}\n")
# save trade result and passed in parameters
if result.retcode == 10009 and self.config.record_trades:
params = params or {}
params['expected_profit'] = profit
date = datetime.utcnow()
date = date.replace(tzinfo=ZoneInfo('UTC'))
params['date'] = date
params['time'] = date.timestamp()
res = Result(result=result, parameters=params)
await res.save_csv()
self.params |= params or {}
await self.send_order()
except Exception as err:
logger.error(f"{err}. Symbol: {self.order.symbol}\n {self.__class__.__name__}.place_trade")
+1
View File
@@ -1,4 +1,5 @@
"""Utility functions for aiomql."""
def dict_to_string(data: dict, multi=False) -> str:
"""Convert a dict to a string. Use for logging.
+1 -1
View File
@@ -2,6 +2,6 @@ from aiomql import TradeAction
class TestConstants:
def test_trade_action(self):
assert TradeAction.DEAL == 1