mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-12 19:38:05 +00:00
v3.15
This commit is contained in:
+4
-2
@@ -69,5 +69,7 @@ target/
|
||||
|
||||
.vscode/
|
||||
|
||||
# config file
|
||||
aiomql.json
|
||||
# config files
|
||||
config.json
|
||||
aiomql.json
|
||||
config/
|
||||
@@ -1,27 +1,33 @@
|
||||
# aiomql
|
||||
# Aiomql - Bot Building Framework and Asynchronous MetaTrader5 Library
|
||||

|
||||

|
||||

|
||||
|
||||
## Installation
|
||||
### Installation
|
||||
```bash
|
||||
pip install aiomql
|
||||
```
|
||||
|
||||
## Key Features
|
||||
- Asynchronous Python Library For MetaTrader 5
|
||||
### Key Features
|
||||
- Asynchronous Python Library For MetaTrader5
|
||||
- Asynchronous Bot Building Framework
|
||||
- Build bots for trading in different financial markets using a bot factory
|
||||
- Use threadpool executors to run multiple strategies on multiple instruments concurrently
|
||||
- Record and keep track of trades and strategies in csv files.
|
||||
- Utility classes for using the MetaTrader 5 Library
|
||||
- Records and keep track of trades and strategies in csv files.
|
||||
- Helper classes for Bot Building. Easy to use and extend.
|
||||
- Compatible with pandas-ta.
|
||||
- Sample Pre-Built strategies
|
||||
- Trade sessions for managing trading sessions
|
||||
- Manage Trading periods using Sessions
|
||||
- Risk Management
|
||||
- Run multiple bots concurrently with different accounts from the same broker or different brokers
|
||||
|
||||
## Simple Usage as an asynchronous MetaTrader5 Libray
|
||||
### As an asynchronous MetaTrader5 Libray
|
||||
```python
|
||||
import asyncio
|
||||
# import the class
|
||||
from aiomql import MetaTrader, Account, TimeFrame, OrderType
|
||||
|
||||
from aiomql import MetaTrader
|
||||
|
||||
|
||||
async def main():
|
||||
mt5 = MetaTrader()
|
||||
await mt5.initialize()
|
||||
@@ -31,54 +37,63 @@ async def main():
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
## As a Bot Building FrameWork using a Sample Strategy
|
||||
|
||||
### As a Bot Building FrameWork using a Sample Strategy
|
||||
***The following code is a sample bot that uses the FingerTrap strategy from the library.\
|
||||
It assumes that you have a config file in the same directory as the script.\
|
||||
The config file should be named aiomql.json and should contain the login details for your account.\
|
||||
It demonstrates the use of sessions and risk management.\
|
||||
Sessions allows you to specify the trading period for a strategy. You can also set an action to be performed at the end of a session.\
|
||||
Risk Management allows you to manage the risk of a strategy. You can set the risk per trade and the risk to reward ratio.\
|
||||
The trader class handles the placing of orders and risk management. It is an attribute of the strategy class.***
|
||||
|
||||
```python
|
||||
from datetime import time
|
||||
import logging
|
||||
|
||||
from aiomql.lib import FingerTrap
|
||||
from aiomql import Bot, Account, ForexSymbol, Session, Sessions, RAM
|
||||
from aiomql import Bot, ForexSymbol, FingerTrap, Session, Sessions, RAM, SimpleTrader, TimeFrame
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def build_bot():
|
||||
# Either initialize an account here with your login details here or set them in the aiomql.json file.
|
||||
# acc = Account(login=1234567, password='*******', server='Broker-Server')
|
||||
bot = Bot()
|
||||
|
||||
# Prebuilt strategy from the library.
|
||||
# Disclaimer: These strategy is only for demonstration purposes.
|
||||
# The author of this library is not responsible for any losses incurred from using this strategy.
|
||||
|
||||
# using trade sessions is optional. the strategy will run with a default session of 24 hours if not specified.
|
||||
# session start and end times are in UTC. Make sure to convert to UTC if you are in a different timezone.
|
||||
# sessions can be used to close positions at the end of a trading session.
|
||||
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)
|
||||
|
||||
# configurable parameters for the strategy
|
||||
params = {'trend_candles_count': 500, 'fast_period': 8}
|
||||
# create sessions for the strategies
|
||||
london = Session(name='London', start=8, end=time(hour=15, minute=30), on_end='close_all')
|
||||
new_york = Session(name='New York', start=13, end=time(hour=20, minute=30))
|
||||
tokyo = Session(name='Tokyo', start=23, end=time(hour=6, minute=30))
|
||||
|
||||
st1 = FingerTrap(symbol=ForexSymbol(name='GBPUSD'), params=params, sessions=sessions)
|
||||
st3 = FingerTrap(symbol=ForexSymbol(name='AUDUSD'), params=params, sessions=sessions)
|
||||
st4 = FingerTrap(symbol=ForexSymbol(name='USDCAD'), params=params, sessions=sessions)
|
||||
st5 = FingerTrap(symbol=ForexSymbol(name='USDJPY'), params=params, sessions=sessions)
|
||||
st6 = FingerTrap(symbol=ForexSymbol(name='EURGBP'), params=params, sessions=sessions)
|
||||
# configure the parameters and the trader for a strategy
|
||||
params = {'trend_candles_count': 500, 'fast_period': 8, 'slow_period': 34, 'entry_timeframe': TimeFrame.M5}
|
||||
gbpusd = ForexSymbol(name='GBPUSD')
|
||||
st1 = FingerTrap(symbol=gbpusd, params=params, trader=SimpleTrader(symbol=gbpusd, ram=RAM(risk=0.05, risk_to_reward=2)),
|
||||
sessions=Sessions(london, new_york))
|
||||
|
||||
# Risk Management
|
||||
ram = RAM(risk=0.05, risk_to_reward=2)
|
||||
# change the risk management of a strategy. This is done on the trader attribute of the strategy.
|
||||
st5.trader.ram = ram
|
||||
# use the default for the other strategies
|
||||
st2 = FingerTrap(symbol=ForexSymbol(name='AUDUSD'), sessions=Sessions(tokyo, new_york))
|
||||
st3 = FingerTrap(symbol=ForexSymbol(name='USDCAD'), sessions=Sessions(new_york))
|
||||
st4 = FingerTrap(symbol=ForexSymbol(name='USDJPY'), sessions=Sessions(tokyo))
|
||||
st5 = FingerTrap(symbol=ForexSymbol(name='EURGBP'), sessions=Sessions(london))
|
||||
|
||||
# sessions are not required
|
||||
st6 = FingerTrap(symbol=ForexSymbol(name='EURUSD'))
|
||||
|
||||
# add strategies to the bot
|
||||
bot.add_strategies([st1, st3, st4, st5, st6])
|
||||
bot.add_strategies([st1, st2, st3, st4, st5, st6])
|
||||
bot.execute()
|
||||
|
||||
|
||||
# run the bot
|
||||
build_bot()
|
||||
```
|
||||
## API Documentation
|
||||
see [API Documentation](https://github.com/Ichinga-Samuel/aiomql/tree/master/docs) for more details
|
||||
|
||||
## Contributing
|
||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||
|
||||
## Support
|
||||
Feeling generous, like the package or want to see it become more a mature package?
|
||||
|
||||
Consider supporting the project by buying me a coffee.\
|
||||
[](https://www.buymeacoffee.com/ichingasamuel)
|
||||
+31
-31
@@ -1,46 +1,36 @@
|
||||
## <a id="account"></a> Account
|
||||
|
||||
- [Account](#Account)
|
||||
- [__aenter__](#Account.__aenter__)
|
||||
- [sign_in](#Account.sign_in)
|
||||
- [refresh](#Account.refresh)
|
||||
- [has_symbol](#Account.has_symbol)
|
||||
- [symbols_get](#Account.symbols_get)
|
||||
- [AccountInfo](#AccountInfo)
|
||||
- [Account](#Account)
|
||||
- [sign_in](#Account.sign_in)
|
||||
- [has_symbol](#Account.has_symbol)
|
||||
- [symbols_get](#Account.symbols_get)
|
||||
-
|
||||
|
||||
<a id="Account"></a>
|
||||
### Account
|
||||
```python
|
||||
class Account(AccountInfo)
|
||||
```
|
||||
Singleton class for managing a trading account. A subclass of [AccountInfo](#accountinfo).
|
||||
Singleton class for managing a trading account. A subclass of [AccountInfo](#AccountInfo).
|
||||
All AccountInfo attributes are available in this class.
|
||||
|
||||
### Attributes:
|
||||
|Name|Type|Description|Default|
|
||||
|---|---|---|---|
|
||||
|**connected**|**bool**|Status of connection to MetaTrader 5 Terminal|False|
|
||||
|symbols|set[SymbolInfo]|A set of available symbols for the financial market.|set()|
|
||||
|
||||
### Notes
|
||||
Other Account properties are defined in the AccountInfo class.
|
||||
|
||||
### refresh
|
||||
```python
|
||||
async def refresh()
|
||||
```
|
||||
Refreshes the account instance with the latest data from the MetaTrader 5 terminal
|
||||
|
||||
### account_info
|
||||
```python
|
||||
@property
|
||||
def account_info() -> dict
|
||||
```
|
||||
Get account login, server and password details. If the login attribute of the account instance returns
|
||||
a falsy value, the config instance is used to get the account details.
|
||||
#### Returns:
|
||||
|Type|Description|
|
||||
|---|---|
|
||||
|**dict**|A dict of login, server and password details|
|
||||
#### Note:
|
||||
This method will only look for config details in the config instance if the login attribute of the account Instance returns a falsy value
|
||||
|
||||
### __aenter__
|
||||
<a id="Account.__aenter__"></a>
|
||||
#### __aenter__
|
||||
```python
|
||||
async def __aenter__() -> 'Account'
|
||||
```
|
||||
Async context manager for the Account class. Connects to a trading account and returns the account instance.
|
||||
|
||||
#### Returns:
|
||||
|Type|Description|
|
||||
|---|---|
|
||||
@@ -50,7 +40,8 @@ Async context manager for the Account class. Connects to a trading account and r
|
||||
|---|---|
|
||||
|**LoginError**|If login fails|
|
||||
|
||||
### sign_in
|
||||
<a id="Account.sign_in"></a>
|
||||
#### sign_in
|
||||
```python
|
||||
async def sign_in() -> bool
|
||||
```
|
||||
@@ -60,7 +51,15 @@ Connect to a trading account.
|
||||
|---|---|
|
||||
|**bool**|True if login was successful else False|
|
||||
|
||||
### has_symbol
|
||||
<a id="Account.refresh"></a>
|
||||
#### refresh
|
||||
```python
|
||||
async def refresh()
|
||||
```
|
||||
Refreshes the account instance with the latest data from the MetaTrader 5 terminal
|
||||
|
||||
<a id="Account.has_symbol"></a>
|
||||
#### has_symbol
|
||||
```python
|
||||
def has_symbol(symbol: str | Type[SymbolInfo])
|
||||
```
|
||||
@@ -74,7 +73,8 @@ Checks to see if a symbol is available for a trading account
|
||||
|---|---|
|
||||
|**bool**|True if symbol is available else False|
|
||||
|
||||
### symbols_get
|
||||
<a id="Account.symbols_get"></a>
|
||||
#### symbols_get
|
||||
```python
|
||||
async def symbols_get() -> set[SymbolInfo]
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
* [MetaTrader](#MetaTrader)
|
||||
* [\_\_aenter\_\_](#__aenter__)
|
||||
* [\_\_aexit\_\_](#aexit)
|
||||
* [\_\_aenter\_\_](#MetaTrader.__aenter__)
|
||||
* [\_\_aexit\_\_](#MetaTrader.__aexit__)
|
||||
* [login](#MetaTrader.login)
|
||||
* [initialize](#MetaTrader.initialize)
|
||||
* [shutdown](#MetaTrader.shutdown)
|
||||
@@ -35,14 +35,25 @@
|
||||
* [history\_deals\_get](#MetaTrader.history_deals_get)
|
||||
|
||||
|
||||
## <a id="MetaTrader"></a> MetaTrader
|
||||
<a id="MetaTrader"></a>
|
||||
### MetaTrader
|
||||
```python
|
||||
class MetaTrader(metaclass=BaseMeta)
|
||||
```
|
||||
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:
|
||||
|Name|Type|Description|Default|
|
||||
|---|---|---|---|
|
||||
|error|Error|The last error encountered by the MetaTrader terminal.|Error(0, '')|
|
||||
|
||||
### <a id="MetaTrader.__aenter__"></a> \_\_aenter\_\_
|
||||
#### Notes:
|
||||
All the attributes, enums and constants of the MetaTrader5 class are also available here. Although, they are more easily
|
||||
accessible and used via the various enums and models defined in the module.
|
||||
|
||||
|
||||
<a id="MetaTrader.__aenter__"></a>
|
||||
#### \_\_aenter\_\_
|
||||
```python
|
||||
async def __aenter__() -> 'MetaTrader'
|
||||
```
|
||||
@@ -54,13 +65,15 @@ Initializes the connection to the MetaTrader terminal.
|
||||
|---|---|
|
||||
|**MetaTrader**|An instance of the MetaTrader class|
|
||||
|
||||
#### <a id="MetaTrader.__aexit__"></a> \_\_aexit\_\_
|
||||
<a id="MetaTrader.__aexit__"></a>
|
||||
#### \_\_aexit\_\_
|
||||
```python
|
||||
async def __aexit__(exc_type, exc_val, exc_tb)
|
||||
```
|
||||
Async context manager exit point. Closes the connection to the MetaTrader terminal.
|
||||
|
||||
#### <a id="MetaTrader.login"></a> login
|
||||
<a id="MetaTrader.login"></a>
|
||||
#### login
|
||||
```python
|
||||
async def login(login: int,
|
||||
password: str,
|
||||
@@ -80,7 +93,8 @@ Connects to the MetaTrader terminal using the specified login, password and serv
|
||||
|---|---|
|
||||
|**bool**|True if successful, False otherwise.|
|
||||
|
||||
#### <a id="MetaTrader.initialize"></a> initialize
|
||||
<a id="MetaTrader.initialize"></a>
|
||||
#### initialize
|
||||
```python
|
||||
async def initialize(path: str = "",
|
||||
login: int = 0,
|
||||
@@ -104,13 +118,15 @@ Initializes the connection to the MetaTrader terminal. All parameters are option
|
||||
|---|---|
|
||||
|**bool**|True if successful, False otherwise.|
|
||||
|
||||
#### <a id="MetaTrader.shutdown"></a> shutdown
|
||||
<a id="MetaTrader.shutdown"></a>
|
||||
#### shutdown
|
||||
```python
|
||||
async def shutdown() -> None
|
||||
```
|
||||
Closes the connection to the MetaTrader terminal.
|
||||
|
||||
#### <a id="MetaTrader.version"></a> version
|
||||
<a id="MetaTrader.version"></a>
|
||||
#### version
|
||||
```python
|
||||
async def version() -> tuple[int, int, str] | None
|
||||
```
|
||||
|
||||
+24
-24
@@ -1,39 +1,39 @@
|
||||
from datetime import time
|
||||
import logging
|
||||
|
||||
from aiomql.lib import FingerTrap
|
||||
from aiomql import Bot, Account, ForexSymbol, Session, Sessions
|
||||
from aiomql import Bot, ForexSymbol, FingerTrap, Session, Sessions, RAM, SimpleTrader, TimeFrame
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def build_bot():
|
||||
# Either initialize an account here with your login details here or set them in the aiomql.json file.
|
||||
# acc = Account(login=1234567, password='*******', server='Broker-Server')
|
||||
bot = Bot()
|
||||
|
||||
# Prebuilt strategy from the library.
|
||||
# Disclaimer: These strategy is only for demonstration purposes.
|
||||
# The author of this library is not responsible for any losses incurred from using this strategy.
|
||||
# create sessions for the strategies
|
||||
london = Session(name='London', start=8, end=time(hour=15, minute=30), on_end='close_all')
|
||||
new_york = Session(name='New York', start=13, end=time(hour=20, minute=30))
|
||||
tokyo = Session(name='Tokyo', start=23, end=time(hour=6, minute=30))
|
||||
|
||||
# using trade sessions is optional. the strategy will run with a default session of 24 hours if not specified.
|
||||
# session start and end times are in UTC. Make sure to convert to UTC if you are in a different timezone.
|
||||
# sessions can be used to close positions at the end of a trading session.
|
||||
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))
|
||||
allsess = Session(name='All', start=0, end=23, on_end='close_all')
|
||||
sessions = Sessions(sess, sess2, sess3, allsess)
|
||||
# configure the parameters and the trader for a strategy
|
||||
params = {'trend_candles_count': 500, 'fast_period': 8, 'slow_period': 34, 'entry_timeframe': TimeFrame.M5}
|
||||
gbpusd = ForexSymbol(name='GBPUSD')
|
||||
st1 = FingerTrap(symbol=gbpusd, params=params,
|
||||
trader=SimpleTrader(symbol=gbpusd, ram=RAM(risk=0.05, risk_to_reward=2)),
|
||||
sessions=Sessions(london, new_york))
|
||||
|
||||
# configurable parameters for the strategy
|
||||
params = {'trend_candles_count': 500, 'fast_period': 8}
|
||||
st1 = FingerTrap(symbol=ForexSymbol(name='GBPUSD'), params=params, sessions=sessions)
|
||||
st3 = FingerTrap(symbol=ForexSymbol(name='AUDUSD'), params=params, sessions=sessions)
|
||||
st4 = FingerTrap(symbol=ForexSymbol(name='USDCAD'), params=params, sessions=sessions)
|
||||
st5 = FingerTrap(symbol=ForexSymbol(name='USDJPY'), params=params, sessions=sessions)
|
||||
st6 = FingerTrap(symbol=ForexSymbol(name='EURGBP'), params=params, sessions=sessions)
|
||||
bot.add_strategies([st1, st3, st4, st5, st6])
|
||||
# use the default for the other strategies
|
||||
st2 = FingerTrap(symbol=ForexSymbol(name='AUDUSD'), sessions=Sessions(tokyo, new_york))
|
||||
st3 = FingerTrap(symbol=ForexSymbol(name='USDCAD'), sessions=Sessions(new_york))
|
||||
st4 = FingerTrap(symbol=ForexSymbol(name='USDJPY'), sessions=Sessions(tokyo))
|
||||
st5 = FingerTrap(symbol=ForexSymbol(name='EURGBP'), sessions=Sessions(london))
|
||||
|
||||
# sessions are not required
|
||||
st6 = FingerTrap(symbol=ForexSymbol(name='EURUSD'))
|
||||
|
||||
# add strategies to the bot
|
||||
bot.add_strategies([st1, st2, st3, st4, st5, st6])
|
||||
bot.execute()
|
||||
|
||||
|
||||
build_bot()
|
||||
# run the bot
|
||||
build_bot()
|
||||
+18
-10
@@ -1,25 +1,32 @@
|
||||
import asyncio
|
||||
from aiomql import Symbol, TimeFrame, Account
|
||||
from aiomql import Symbol, TimeFrame, Account, Candle, Candles
|
||||
|
||||
|
||||
async def main():
|
||||
"""Example of using the Candle and Candles classes.
|
||||
The candle class is a single price bar. Holding the OHLCV data for a single price bar.
|
||||
The Candles class is a container of Candle objects. It is an Iterable of Candle objects.
|
||||
It is sliceable and indexable. It can also be accessed with keywords.
|
||||
It is a wrapper around a pandas DataFrame. Which is what it uses to store the data.
|
||||
"""
|
||||
async with Account():
|
||||
# create a symbol
|
||||
sym = Symbol(name="AUDUSD")
|
||||
sym = Symbol(name="EURUSD")
|
||||
|
||||
# Get EURUSD price bars for the past 48 hours
|
||||
candles = await sym.copy_rates_from_pos(timeframe=TimeFrame.H1, count=48, start_position=0)
|
||||
candles: Candles = await sym.copy_rates_from_pos(timeframe=TimeFrame.H1, count=48, start_position=0)
|
||||
|
||||
# get size of candles
|
||||
print(len(candles)) # 48
|
||||
|
||||
# get the latest candle by accessing the last one.
|
||||
last = candles[-1] # A Candle object
|
||||
last: Candle = candles[-1] # A Candle object
|
||||
print(type(last))
|
||||
print(last.time)
|
||||
print(last.Index)
|
||||
|
||||
# get the last five hours
|
||||
last_five = candles[-5:] # A Candles object.
|
||||
print(type(last_five))
|
||||
print(last_five)
|
||||
# slicing returns a Candles object
|
||||
half = candles[24:]
|
||||
print(type(half))
|
||||
print(len(half))
|
||||
|
||||
close = candles['close'] # close price of all the candles as a pandas series
|
||||
print(type(close))
|
||||
@@ -32,6 +39,7 @@ async def main():
|
||||
|
||||
# use talib to compute crossover. This returns a series object that is not part of the candles object.
|
||||
closeXema = candles.ta_lib.cross(candles.close, candles.ema)
|
||||
|
||||
# add to the candles
|
||||
candles['closeXema'] = closeXema
|
||||
print(candles)
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ async def main():
|
||||
async with Account():
|
||||
|
||||
# create a symbol
|
||||
sym = ForexSymbol(name="EURUSD")
|
||||
sym = ForexSymbol(name="EURUSD-T")
|
||||
|
||||
# Confirm the symbol is available for this account and initialize with default values.
|
||||
res = await sym.init()
|
||||
@@ -15,7 +15,7 @@ async def main():
|
||||
# I want to place a market buy order, risk only 2usd, and target 10 pips in this trade.
|
||||
# The ForexSymbol object has a compute_volume method that can be used to compute the volume
|
||||
# given a target pips and amount.
|
||||
volume = await sym.compute_volume(amount=2, pips=10)
|
||||
volume = await sym.compute_volume(amount=2, points=100)
|
||||
|
||||
# a risk to reward ratio of 1:2
|
||||
# get the price tick of the symbol
|
||||
@@ -34,4 +34,4 @@ async def main():
|
||||
print(res)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
asyncio.run(main())
|
||||
@@ -1,25 +1,27 @@
|
||||
import logging
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from aiomql import ForexSymbol, Account, Positions, History, Trader, OrderType, RAM
|
||||
from aiomql import ForexSymbol, Account, Positions, History, SimpleTrader as Trader, OrderType, RAM
|
||||
|
||||
logging.basicConfig(level=logging.INFO, filemode='w', filename='example.log', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
async def main():
|
||||
# Account details are in the aiomql.json file
|
||||
async with Account():
|
||||
|
||||
# get start time using local timezone
|
||||
tz = datetime.now().astimezone().tzinfo
|
||||
start = datetime.now(tz=tz)
|
||||
# get start time
|
||||
start = datetime.now()
|
||||
|
||||
# create two symbols and initialize them
|
||||
sym1 = ForexSymbol(name="EURUSD")
|
||||
sym2 = ForexSymbol(name="GBPUSD")
|
||||
sym1 = ForexSymbol(name="EURUSD-T")
|
||||
sym2 = ForexSymbol(name="GBPUSD-T")
|
||||
await sym1.init()
|
||||
await sym2.init()
|
||||
|
||||
# Risk Assets Management instance
|
||||
# fix the amount to be risked at 2 USD. USD is the account currency.
|
||||
ram = RAM(amount=2)
|
||||
ram = RAM(amount=2, points=100)
|
||||
|
||||
# Create two traders instance
|
||||
trd = Trader(symbol=sym1, ram=ram)
|
||||
@@ -38,22 +40,23 @@ async def main():
|
||||
|
||||
# close all open positions
|
||||
await pos.close_all()
|
||||
end = datetime.now(tz=tz)
|
||||
end = datetime.now()
|
||||
|
||||
# get the number of open positions
|
||||
total = await pos.positions_total()
|
||||
print(f'{total} Open positions') # 0
|
||||
print(f'{total} Open positions')
|
||||
|
||||
# get historical trades
|
||||
his = History(date_from=start, date_to=end)
|
||||
|
||||
# get the number of deals
|
||||
total_deals = await his.deals_total()
|
||||
print(f'{total_deals} Deals')
|
||||
start = datetime(day=start.day-1, month=start.month, year=start.year, hour=start.hour, minute=0, second=0)
|
||||
his = History(date_from=start.timestamp(), date_to=end.timestamp())
|
||||
|
||||
# get the number of order
|
||||
orders = await his.orders_total()
|
||||
print(f'{orders} orders')
|
||||
|
||||
# get the number of deals
|
||||
# total_deals = await his.deals_total()
|
||||
# print(f'{total_deals} Deals')
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
asyncio.run(main())
|
||||
+4
-2
@@ -1,10 +1,12 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from aiomql import ForexSymbol, Symbol, TimeFrame, Account
|
||||
from aiomql import ForexSymbol, TimeFrame, Account, Config
|
||||
|
||||
|
||||
config = Config()
|
||||
async def main():
|
||||
async with Account():
|
||||
sym = ForexSymbol(name="EURUSD")
|
||||
sym = ForexSymbol(name="EURUSD-T")
|
||||
res = await sym.init()
|
||||
if not res:
|
||||
print('Symbol not available')
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "aiomql"
|
||||
version = "3.12"
|
||||
version = "3.14"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .core import *
|
||||
from .account import Account
|
||||
from .ram import RAM
|
||||
from .symbol import Symbol
|
||||
@@ -14,10 +15,5 @@ from .history import History
|
||||
from .trader import Trader
|
||||
from .terminal import Terminal
|
||||
from .sessions import Session, Sessions
|
||||
|
||||
from .core.config import Config
|
||||
from .core.constants import *
|
||||
from .core.meta_trader import MetaTrader
|
||||
from .core.models import *
|
||||
from .core.exceptions import *
|
||||
from .lib import *
|
||||
from .utils import dict_to_string
|
||||
from .lib import *
|
||||
+10
-18
@@ -1,5 +1,4 @@
|
||||
from logging import getLogger
|
||||
from typing import Type
|
||||
|
||||
from .core.models import AccountInfo, SymbolInfo
|
||||
from .core.exceptions import LoginError
|
||||
@@ -18,6 +17,7 @@ class Account(AccountInfo):
|
||||
Notes:
|
||||
Other Account properties are defined in the AccountInfo class.
|
||||
"""
|
||||
_instance: 'Account'
|
||||
connected: bool
|
||||
symbols = set()
|
||||
|
||||
@@ -26,27 +26,18 @@ class Account(AccountInfo):
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if not self.login:
|
||||
acc = self.config.account_info()
|
||||
self.set_attributes(**acc)
|
||||
|
||||
async def refresh(self):
|
||||
"""Refreshes the account instance with the latest account details from the MetaTrader 5 terminal"""
|
||||
account_info = await self.mt5.account_info()
|
||||
acc = account_info._asdict()
|
||||
self.set_attributes(**acc)
|
||||
|
||||
@property
|
||||
def account_info(self) -> dict:
|
||||
"""Get account login, server and password details. If the login attribute of the account instance returns
|
||||
a falsy value, the config instance is used to get the account details.
|
||||
|
||||
Returns:
|
||||
dict: A dict of login, server and password details
|
||||
|
||||
Note:
|
||||
This method will only look for config details in the config instance if the login attribute of the
|
||||
account Instance returns a falsy value
|
||||
"""
|
||||
acc_info = self.get_dict(include={'login', 'server', 'password'})
|
||||
return acc_info if acc_info['login'] else self.config.account_info()
|
||||
|
||||
async def __aenter__(self) -> 'Account':
|
||||
"""Connect to a trading account and return the account instance.
|
||||
Async context manager for the Account class.
|
||||
@@ -72,8 +63,9 @@ class Account(AccountInfo):
|
||||
Returns:
|
||||
bool: True if login was successful else False
|
||||
"""
|
||||
await self.mt5.initialize(**self.account_info)
|
||||
self.connected = await self.mt5.login(**self.account_info)
|
||||
acc = self.get_dict(include={'login', 'server', 'password'})
|
||||
await self.mt5.initialize(**acc, path=self.config.path)
|
||||
self.connected = await self.mt5.login(**acc)
|
||||
if self.connected:
|
||||
await self.refresh()
|
||||
self.symbols = await self.symbols_get()
|
||||
|
||||
@@ -4,6 +4,7 @@ import logging
|
||||
|
||||
from .executor import Executor
|
||||
from .account import Account
|
||||
from .core.config import Config
|
||||
from .symbol import Symbol as _Symbol
|
||||
from .strategy import Strategy as _Strategy
|
||||
|
||||
@@ -20,11 +21,17 @@ class Bot:
|
||||
account (Account): Account Object.
|
||||
executor: The default thread executor.
|
||||
symbols (list[Symbols]): A set of symbols for the trading session
|
||||
"""
|
||||
config (Config): Config instance
|
||||
|
||||
account: Account = Account()
|
||||
"""
|
||||
config: Config
|
||||
account: Account
|
||||
symbols: set
|
||||
executor: Executor
|
||||
|
||||
def __init__(self):
|
||||
self.config = Config()
|
||||
self.account = Account()
|
||||
self.symbols = set()
|
||||
self.executor = Executor(bot=self)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from typing import Type, TypeVar, Generic, Iterable
|
||||
from logging import getLogger
|
||||
import reprlib
|
||||
|
||||
from pandas import DataFrame, Series
|
||||
import pandas_ta as ta
|
||||
|
||||
+9
-29
@@ -16,16 +16,15 @@ class Base:
|
||||
|
||||
Keyword Args:
|
||||
**kwargs: Object attributes and values as keyword arguments. Only added if they are annotated on the class body.
|
||||
|
||||
Class Attributes:
|
||||
mt5 (MetaTrader): An instance of the MetaTrader class
|
||||
config (Config): An instance of the Config class
|
||||
Meta (Type[Meta]): The Meta class for configuration of the data model class
|
||||
"""
|
||||
mt5: MetaTrader = MetaTrader()
|
||||
config = Config()
|
||||
mt5: MetaTrader
|
||||
config: Config
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.config = Config()
|
||||
self.mt5 = MetaTrader()
|
||||
self.exclude = {'mt5', "config", 'exclude', 'include', 'annotations', 'class_vars', 'dict'}
|
||||
self.include = set()
|
||||
self.set_attributes(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -114,27 +113,8 @@ class Base:
|
||||
dict: A dictionary of instance and class attributes
|
||||
"""
|
||||
try:
|
||||
_filter = self.exclude.difference(self.include)
|
||||
return {key: value for key, value in (self.class_vars | self.__dict__).items() if
|
||||
key not in self.Meta.filter}
|
||||
key not in _filter}
|
||||
except Exception as err:
|
||||
logger.warning(err)
|
||||
|
||||
class Meta:
|
||||
"""A class for defining class attributes to be excluded or included in the dict property
|
||||
|
||||
Attributes:
|
||||
exclude (set): A set of attributes to be excluded
|
||||
include (set): Specific attributes to be returned. Include supercedes exclude.
|
||||
"""
|
||||
exclude = {'mt5', "Config"}
|
||||
include = set()
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def filter(cls) -> set:
|
||||
"""Combine the exclude and include attributes to return a set of attributes to be excluded.
|
||||
|
||||
Returns:
|
||||
set: A set of attributes to be excluded
|
||||
"""
|
||||
return cls.exclude.difference(cls.include)
|
||||
logger.warning(err)
|
||||
+16
-12
@@ -25,7 +25,7 @@ class Config:
|
||||
server (str): Broker server
|
||||
path (str): Path to terminal file
|
||||
timeout (int): Timeout for terminal connection
|
||||
|
||||
_initialize (bool): First time initialization flag
|
||||
Notes:
|
||||
By default, the config class looks for a file named aiomql.json.
|
||||
You can change this by passing the filename keyword argument to the constructor.
|
||||
@@ -38,10 +38,11 @@ class Config:
|
||||
path: str = ""
|
||||
timeout: int = 60000
|
||||
record_trades: bool = True
|
||||
filename: str = "aiomql.json"
|
||||
filename: str
|
||||
win_percentage: float = 0.85
|
||||
records_dir = Path.home() / "Documents" / "Aiomql" / "Trade Records"
|
||||
_load = 1
|
||||
config_dir: str = ''
|
||||
_initialize = True
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if not hasattr(cls, "_instance"):
|
||||
@@ -49,8 +50,10 @@ class Config:
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.load_config(reload=False)
|
||||
[setattr(self, key, value) for key, value in kwargs]
|
||||
self.filename = kwargs.pop('filename', "aiomql.json")
|
||||
self.config_dir = kwargs.pop('config_dir', '')
|
||||
self.load_config(reload=kwargs.pop('reload', False))
|
||||
[setattr(self, key, value) for key, value in kwargs.items()]
|
||||
|
||||
@staticmethod
|
||||
def walk_to_root(path: str) -> Iterator[str]:
|
||||
@@ -76,6 +79,7 @@ class Config:
|
||||
frame = frame.f_back
|
||||
frame_filename = frame.f_code.co_filename
|
||||
path = os.path.dirname(os.path.abspath(frame_filename))
|
||||
path = os.path.join(path, self.config_dir) if self.config_dir else path
|
||||
|
||||
for dirname in self.walk_to_root(path):
|
||||
check_path = os.path.join(dirname, self.filename)
|
||||
@@ -83,14 +87,14 @@ class Config:
|
||||
return check_path
|
||||
return None
|
||||
|
||||
def load_config(self, file: str = None, reload: bool = True):
|
||||
if reload:
|
||||
self._load = 1
|
||||
if self._load != 1:
|
||||
def load_config(self, file: str = None, reload: bool = True, filename: str = None, config_dir: str = ''):
|
||||
"""Load configuration settings from a file."""
|
||||
if not (self._initialize or reload):
|
||||
return
|
||||
|
||||
self._load = 0
|
||||
self._initialize = False
|
||||
data = {}
|
||||
self.filename = filename or self.filename
|
||||
self.config_dir = config_dir or self.config_dir
|
||||
if (file := (file or self.find_config())) is None:
|
||||
logger.warning("No Config File Found")
|
||||
else:
|
||||
@@ -100,7 +104,7 @@ class Config:
|
||||
[setattr(self, key, value) for key, value in data.items()]
|
||||
self.records_dir.mkdir(parents=True, exist_ok=True) if self.records_dir else ...
|
||||
|
||||
def account_info(self) -> dict["login", "password", "server"]:
|
||||
def account_info(self) -> dict[str, int | str]:
|
||||
"""Returns Account login details as found in the config object if available
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -19,6 +19,7 @@ class Error:
|
||||
-10004: 'internal IPC no ipc',
|
||||
-10005: 'internal timeout',
|
||||
}
|
||||
|
||||
def __init__(self, code: int, description: str = ''):
|
||||
self.code = code
|
||||
self.description = description or self.descriptions.get(code, 'Unknown Error')
|
||||
|
||||
@@ -56,6 +56,11 @@ class MetaTrader(metaclass=BaseMeta):
|
||||
_symbols_total: Callable
|
||||
_terminal_info: Callable
|
||||
_version: Callable
|
||||
error: Error
|
||||
config: Config
|
||||
|
||||
def __init__(self):
|
||||
self.config = Config()
|
||||
|
||||
async def __aenter__(self) -> 'MetaTrader':
|
||||
"""
|
||||
@@ -120,33 +125,37 @@ class MetaTrader(metaclass=BaseMeta):
|
||||
return await asyncio.to_thread(self._shutdown)
|
||||
|
||||
async def last_error(self) -> tuple[int, str]:
|
||||
return await asyncio.to_thread(self._last_error)
|
||||
try:
|
||||
return await asyncio.to_thread(self._last_error)
|
||||
except Exception as err:
|
||||
logger.warning(f'Error in obtaining last error.')
|
||||
return 0, str(err)
|
||||
|
||||
async def version(self) -> tuple[int, int, str] | None:
|
||||
""""""
|
||||
res = await asyncio.to_thread(self._version)
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining version information.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining version information.{self.error.description}')
|
||||
return res
|
||||
|
||||
async def account_info(self) -> AccountInfo | None:
|
||||
""""""
|
||||
res = await asyncio.to_thread(self._account_info)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining account information.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining account information.{self.error.description}')
|
||||
return res
|
||||
|
||||
async def terminal_info(self) -> TerminalInfo | None:
|
||||
res = await asyncio.to_thread(self._terminal_info)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining terminal information.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining terminal information.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def symbols_total(self) -> int:
|
||||
@@ -155,32 +164,29 @@ class MetaTrader(metaclass=BaseMeta):
|
||||
async def symbols_get(self, group: str = "") -> tuple[SymbolInfo] | None:
|
||||
kwargs = {'group': group} if group else {}
|
||||
res = await asyncio.to_thread(self._symbols_get, **kwargs)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining symbols.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining symbols.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def symbol_info(self, symbol: str) -> SymbolInfo | None:
|
||||
res = await asyncio.to_thread(self._symbol_info, symbol)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining information for {symbol}.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining information for {symbol}.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def symbol_info_tick(self, symbol: str) -> Tick | None:
|
||||
res = await asyncio.to_thread(self._symbol_info_tick, symbol)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining tick for {symbol}.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining tick for {symbol}.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def symbol_select(self, symbol: str, enable: bool) -> bool:
|
||||
@@ -191,23 +197,22 @@ class MetaTrader(metaclass=BaseMeta):
|
||||
|
||||
async def market_book_get(self, symbol: str) -> tuple[BookInfo] | None:
|
||||
res = await asyncio.to_thread(self._market_book_get, symbol)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining market depth content for {symbol}.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining market depth content for {symbol}.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def market_book_release(self, symbol: str) -> bool:
|
||||
return await asyncio.to_thread(self._market_book_release, symbol)
|
||||
|
||||
async def copy_rates_from(self, symbol: str, timeframe: TimeFrame, date_from: datetime | int, count: int):
|
||||
async def copy_rates_from(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float, count: int):
|
||||
res = await asyncio.to_thread(self._copy_rates_from, symbol, timeframe, date_from, count)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining rates for {symbol}.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining rates for {symbol}.{self.error.description}')
|
||||
return res
|
||||
return res
|
||||
|
||||
@@ -215,39 +220,37 @@ class MetaTrader(metaclass=BaseMeta):
|
||||
res = await asyncio.to_thread(self._copy_rates_from_pos, symbol, timeframe, start_pos, count)
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining rates for {symbol}.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining rates for {symbol}.{self.error.description}')
|
||||
return res
|
||||
return res
|
||||
|
||||
async def copy_rates_range(self, symbol: str, timeframe: TimeFrame, date_from: datetime | int,
|
||||
date_to: datetime | int):
|
||||
async def copy_rates_range(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float,
|
||||
date_to: datetime | float):
|
||||
res = await asyncio.to_thread(self._copy_rates_range, symbol, timeframe, date_from, date_to)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining rates for {symbol}.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining rates for {symbol}.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def copy_ticks_from(self, symbol: str, date_from: datetime | int, count: int, flags: CopyTicks):
|
||||
async def copy_ticks_from(self, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks):
|
||||
res = await asyncio.to_thread(self._copy_ticks_from, symbol, date_from, count, flags)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining ticks for {symbol}.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining ticks for {symbol}.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def copy_ticks_range(self, symbol: str, date_from: datetime | int, date_to: datetime | int, flags: CopyTicks):
|
||||
async def copy_ticks_range(self, symbol: str, date_from: datetime | float, date_to: datetime | float, flags: CopyTicks):
|
||||
res = await asyncio.to_thread(self._copy_ticks_range, symbol, date_from, date_to, flags)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining ticks for {symbol}.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining ticks for {symbol}.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def orders_total(self) -> int:
|
||||
@@ -270,33 +273,30 @@ class MetaTrader(metaclass=BaseMeta):
|
||||
"""
|
||||
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('symbol', symbol)) if value}
|
||||
res = await asyncio.to_thread(self._orders_get, **kwargs)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining orders.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining orders.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def order_calc_margin(self, action: OrderType, symbol: str, volume: float, price: float) -> float | None:
|
||||
res = await asyncio.to_thread(self._order_calc_margin, action, symbol, volume, price)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in calculating margin.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in calculating margin.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def order_calc_profit(self, action: OrderType, symbol: str, volume: float, price_open: float,
|
||||
price_close: float) -> float | None:
|
||||
res = await asyncio.to_thread(self._order_calc_profit, action, symbol, volume, price_open, price_close)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in calculating profit.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in calculating profit.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def order_check(self, request: dict) -> OrderCheckResult:
|
||||
@@ -311,41 +311,39 @@ class MetaTrader(metaclass=BaseMeta):
|
||||
async def positions_get(self, group: str = "", ticket: int = 0, symbol: str = "") -> tuple[TradePosition] | None:
|
||||
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('symbol', symbol)) if value}
|
||||
res = await asyncio.to_thread(self._positions_get, **kwargs)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in obtaining open positions.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in obtaining open positions.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def history_orders_total(self, date_from: datetime | int, date_to: datetime | int) -> int:
|
||||
async def history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
|
||||
return await asyncio.to_thread(self._history_orders_total, date_from, date_to)
|
||||
|
||||
async def history_orders_get(self, date_from: datetime | int = None, date_to: datetime | int = None, group: str = '',
|
||||
async def history_orders_get(self, date_from: datetime | float = None, date_to: datetime | float = None, group: str = '',
|
||||
ticket: int = 0, position: int = 0) -> tuple[TradeOrder] | None:
|
||||
kwargs = {key: value for key, value in (('date_from', date_from), ('date_to', date_to), ('group', group),
|
||||
('ticket', ticket), ('position', position)) if value}
|
||||
res = await asyncio.to_thread(self._history_orders_get, **kwargs)
|
||||
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in getting orders.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in getting orders.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
|
||||
async def history_deals_total(self, date_from: datetime | int, date_to: datetime | int) -> int:
|
||||
async def history_deals_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
|
||||
return await asyncio.to_thread(self._history_deals_total, date_from, date_to)
|
||||
|
||||
async def history_deals_get(self, date_from: datetime | int = None, date_to: datetime | int = None, group: str = '',
|
||||
ticket: int = 0, position: int = 0) -> tuple[TradeDeal] | None:
|
||||
async def history_deals_get(self, date_from: datetime | float = None, date_to: datetime | float = None,
|
||||
group: str = '', ticket: int = 0, position: int = 0) -> tuple[TradeDeal] | None:
|
||||
kwargs = {key: value for key, value in (('date_from', date_from), ('date_to', date_to), ('group', group),
|
||||
('ticket', ticket), ('position', position)) if value}
|
||||
res = await asyncio.to_thread(self._history_deals_get, **kwargs)
|
||||
if res is None:
|
||||
err = await self.last_error()
|
||||
logger.warning(f'Error in getting deals.{Error(*err)}')
|
||||
self.error = Error(*err)
|
||||
logger.warning(f'Error in getting deals.{self.error.description}')
|
||||
return res
|
||||
|
||||
return res
|
||||
+12
-8
@@ -24,8 +24,8 @@ class History:
|
||||
mt5 (MetaTrader): MetaTrader instance
|
||||
config (Config): Config instance
|
||||
"""
|
||||
mt5: MetaTrader = MetaTrader()
|
||||
config: Config = Config()
|
||||
mt5: MetaTrader
|
||||
config: Config
|
||||
|
||||
def __init__(self, *, date_from: datetime | float = None, date_to: datetime | float = None,
|
||||
group: str = "", ticket: int = 0, position: int = 0):
|
||||
@@ -41,6 +41,8 @@ class History:
|
||||
ticket (int): Filter for selecting history by ticket number
|
||||
position (int): Filter for selecting history deals by position
|
||||
"""
|
||||
self.config = Config()
|
||||
self.mt5 = MetaTrader()
|
||||
self.date_from = date_from
|
||||
self.date_to = date_to
|
||||
self.group = group
|
||||
@@ -77,11 +79,12 @@ class History:
|
||||
"""
|
||||
deals = await self.mt5.history_deals_get(date_from=self.date_from, date_to=self.date_to, position=self.position,
|
||||
group=self.group, ticket=self.ticket)
|
||||
if deals is not None:
|
||||
self.deals = [TradeDeal(**deal._asdict()) for deal in deals] if deals else []
|
||||
self.total_deals = len(self.deals)
|
||||
return self.deals
|
||||
if deals is None:
|
||||
logger.warning(f'Failed to get deals due to {self.mt5.error.description}')
|
||||
deals = []
|
||||
|
||||
self.deals = [TradeDeal(**deal._asdict()) for deal in deals]
|
||||
self.total_deals = len(self.deals)
|
||||
return self.deals
|
||||
|
||||
async def deals_total(self) -> int:
|
||||
@@ -103,7 +106,8 @@ class History:
|
||||
orders = await self.mt5.history_orders_get(date_from=self.date_from, date_to=self.date_to, group=self.group,
|
||||
position=self.position, ticket=self.ticket)
|
||||
if orders is None:
|
||||
return self.orders
|
||||
logger.warning(f'Failed to get orders due to {self.mt5.error.description}')
|
||||
orders = []
|
||||
|
||||
self.orders = [TradeOrder(**order._asdict()) for order in orders]
|
||||
self.total_orders = len(self.orders)
|
||||
@@ -116,4 +120,4 @@ class History:
|
||||
int: Total number of orders
|
||||
"""
|
||||
self.total_orders = await self.mt5.history_orders_total(self.date_from, self.date_to)
|
||||
return self.total_orders
|
||||
return self.total_orders
|
||||
@@ -25,7 +25,7 @@ class FingerTrap(Strategy):
|
||||
trend_candles_count: int
|
||||
trader: Trader
|
||||
tracker: Tracker
|
||||
_parameters = {"trend": 3, "fast_period": 8, "slow_period": 34, "entry_time_frame": TimeFrame.M5,
|
||||
parameters = {"trend": 3, "fast_period": 8, "slow_period": 34, "entry_time_frame": TimeFrame.M5,
|
||||
"trend_time_frame": TimeFrame.H1, "entry_period": 8,
|
||||
"trend_candles_count": 48, "entry_candles_count": 50}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class ForexSymbol(Symbol):
|
||||
|
||||
Args:
|
||||
amount (float): Amount to risk. Given in terms of the account currency.
|
||||
points (float): Target pips.
|
||||
points (float): Target points.
|
||||
use_limits (bool): If True, the computed volume checked against the maximum and minimum volume.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""Trader class module. Handles the creation of an order and the placing of trades"""
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from ..symbols import ForexSymbol
|
||||
@@ -13,49 +11,43 @@ logger = getLogger(__name__)
|
||||
|
||||
class SimpleTrader(Trader):
|
||||
"""A simple trader class. Limits the number of loosing trades per symbol"""
|
||||
def __init__(self, *, symbol: ForexSymbol, ram: RAM = None, num_trades: int = 1):
|
||||
def __init__(self, *, symbol: ForexSymbol, ram: RAM = None, loss_limit: int = 3):
|
||||
"""Initializes the order object and RAM instance
|
||||
The default risk to reward ratio is 1:1.
|
||||
|
||||
Args:
|
||||
symbol (Symbol): Financial instrument
|
||||
ram (RAM): Risk Assessment and Management instance
|
||||
num_trades (int): Number of open trades in loosing positions to allow per symbol
|
||||
loss_limit (int): Maximum number of losing trades allowed at a time.
|
||||
"""
|
||||
ram = ram or RAM(risk_to_reward=1, points=100)
|
||||
super().__init__(symbol=symbol, ram=ram)
|
||||
self.positions = Positions(symbol=symbol.name)
|
||||
self.num_trades = num_trades
|
||||
self.loss_limit = loss_limit
|
||||
|
||||
async def create_order(self, *, order_type: OrderType, points: float = 0):
|
||||
async def create_order(self, *, order_type: OrderType):
|
||||
"""Complete the order object with the required values. Creates a simple order.
|
||||
|
||||
Args:
|
||||
order_type (OrderType): Type of order
|
||||
points (float): Target points
|
||||
"""
|
||||
positions = await self.positions.positions_get()
|
||||
positions.sort(key=lambda pos: pos.time_msc)
|
||||
positions = await Positions().positions_get()
|
||||
loosing = [trade for trade in positions if trade.profit < 0]
|
||||
if (losses := len(loosing)) > self.num_trades:
|
||||
if (losses := len(loosing)) > self.loss_limit:
|
||||
raise RuntimeError(f"Last {losses} trades in a losing position")
|
||||
points = points or self.symbol.trade_stops_level * 2
|
||||
amount = self.ram.amount or await self.ram.get_amount()
|
||||
points = self.ram.points or self.symbol.trade_stops_level * 3
|
||||
amount = await self.ram.get_amount()
|
||||
self.order.volume = await self.symbol.compute_volume(amount=amount, points=points)
|
||||
self.order.type = order_type
|
||||
self.order.comment = self.parameters.get('name', '')
|
||||
await self.set_trade_stop_levels(points=points)
|
||||
|
||||
async def place_trade(self, order_type: OrderType, parameters: dict = None, points: float = 0):
|
||||
"""Places a trade based on the order_type.
|
||||
|
||||
Args:
|
||||
order_type (OrderType): Type of order
|
||||
parameters: parameters of the trading strategy used to place the trade
|
||||
points (float): Target points
|
||||
"""
|
||||
async def place_trade(self, order_type: OrderType, parameters: dict = None):
|
||||
"""Places a trade based on the order_type."""
|
||||
try:
|
||||
self.parameters |= parameters or {}
|
||||
await self.create_order(order_type=order_type, points=points)
|
||||
await self.create_order(order_type=order_type)
|
||||
if not await self.check_order():
|
||||
return
|
||||
await self.send_order()
|
||||
except Exception as err:
|
||||
logger.error(f"{err}. Symbol: {self.order.symbol}\n {self.__class__.__name__}.place_trade")
|
||||
logger.error(f"{err} in {self.__class__.__name__}.place_trade for {self.symbol.name}")
|
||||
+6
-5
@@ -51,6 +51,8 @@ class Order(TradeRequest):
|
||||
tuple[TradeOrder]: A Tuple of active trade orders as TradeOrder objects
|
||||
"""
|
||||
orders = await self.mt5.orders_get(symbol=self.symbol)
|
||||
if orders is None:
|
||||
raise OrderError(f'Failed to get orders for {self.symbol} due to {self.mt5.error.description}')
|
||||
orders = (TradeOrder(**order._asdict()) for order in orders)
|
||||
return tuple(orders)
|
||||
|
||||
@@ -65,7 +67,7 @@ class Order(TradeRequest):
|
||||
"""
|
||||
res = await self.mt5.order_check(self.dict)
|
||||
if res is None:
|
||||
raise OrderError(f'Failed to check order {self.symbol} {self.type} {self.volume} {self.price} {res}')
|
||||
raise OrderError(f'Failed to check order due to {self.mt5.error.description}')
|
||||
return OrderCheckResult(**res._asdict())
|
||||
|
||||
async def send(self) -> OrderSendResult:
|
||||
@@ -79,7 +81,7 @@ class Order(TradeRequest):
|
||||
"""
|
||||
res = await self.mt5.order_send(self.dict)
|
||||
if res is None:
|
||||
raise OrderError(f'Failed to send order {self.symbol} {self.type} {self.volume} {self.price}')
|
||||
raise OrderError(f'Failed to send order {self.symbol} due to {self.mt5.error.description}')
|
||||
return OrderSendResult(**res._asdict())
|
||||
|
||||
async def calc_margin(self) -> float:
|
||||
@@ -93,7 +95,7 @@ class Order(TradeRequest):
|
||||
"""
|
||||
res = await self.mt5.order_calc_margin(self.type, self.symbol, self.volume, self.price)
|
||||
if res is None:
|
||||
raise OrderError(f'Failed to calculate margin for {self.symbol} {self.type} {self.volume} {self.price} {res}')
|
||||
raise OrderError(f'Failed to calculate margin for {self.symbol} due to {self.mt5.error.description}')
|
||||
return res
|
||||
|
||||
async def calc_profit(self) -> float:
|
||||
@@ -107,6 +109,5 @@ class Order(TradeRequest):
|
||||
"""
|
||||
res = await self.mt5.order_calc_profit(self.type, self.symbol, self.volume, self.price, self.tp)
|
||||
if res is None:
|
||||
raise OrderError(
|
||||
f'Failed to calculate profit for {self.symbol} {self.type} {self.volume} {self.price} {self.tp}')
|
||||
raise OrderError(f'Failed to calculate profit for {self.symbol} due to {self.mt5.error.description}')
|
||||
return res
|
||||
@@ -18,7 +18,7 @@ class Positions:
|
||||
ticket (int): Position ticket.
|
||||
mt5 (MetaTrader): MetaTrader instance.
|
||||
"""
|
||||
mt5: MetaTrader = MetaTrader()
|
||||
mt5: MetaTrader
|
||||
|
||||
def __init__(self, *, symbol: str = "", group: str = "", ticket: int = 0):
|
||||
"""Get Open Positions.
|
||||
@@ -30,6 +30,7 @@ class Positions:
|
||||
ticket (int): Position ticket
|
||||
|
||||
"""
|
||||
self.mt5 = MetaTrader()
|
||||
self.symbol = symbol
|
||||
self.group = group
|
||||
self.ticket = ticket
|
||||
@@ -42,7 +43,7 @@ class Positions:
|
||||
"""
|
||||
return await self.mt5.positions_total()
|
||||
|
||||
async def positions_get(self, symbol: str = '', group: str = '', ticket: int = 0):
|
||||
async def positions_get(self, symbol: str = '', group: str = '', ticket: int = 0) -> list[TradePosition]:
|
||||
"""Get open positions with the ability to filter by symbol or ticket.
|
||||
|
||||
Keyword Args:
|
||||
@@ -56,8 +57,9 @@ class Positions:
|
||||
"""
|
||||
positions = await self.mt5.positions_get(group=group or self.group, symbol=symbol or self.symbol,
|
||||
ticket=ticket or self.ticket)
|
||||
if not positions:
|
||||
return []
|
||||
if positions is None:
|
||||
logger.warning(f'Failed to get positions for {symbol or self.symbol} due to {self.mt5.error.description}')
|
||||
positions = []
|
||||
return [TradePosition(**pos._asdict()) for pos in positions]
|
||||
|
||||
async def close(self, *, ticket: int, symbol: str, price: float, volume: float, order_type: OrderType):
|
||||
@@ -84,5 +86,4 @@ class Positions:
|
||||
symbol=pos.symbol) for pos in positions]
|
||||
|
||||
results = await asyncio.gather(*[order for order in orders], return_exceptions=True)
|
||||
amount_closed = len([res for res in results if res.retcode == 10009])
|
||||
return amount_closed
|
||||
return len([res for res in results if res.retcode == 10009])
|
||||
+6
-7
@@ -3,12 +3,14 @@ from .account import Account
|
||||
|
||||
|
||||
class RAM:
|
||||
account: Account = Account()
|
||||
account: Account
|
||||
risk_to_reward: float
|
||||
risk: float
|
||||
amount: float
|
||||
points: float
|
||||
pips: float
|
||||
min_amount: float
|
||||
max_amount: float
|
||||
|
||||
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.
|
||||
@@ -22,17 +24,14 @@ class RAM:
|
||||
self.risk_to_reward = risk_to_reward
|
||||
self.risk = risk
|
||||
self.amount = amount
|
||||
self.account = Account()
|
||||
[setattr(self, key, value) for key, value in kwargs.items()]
|
||||
|
||||
async def get_amount(self, risk: float = 0) -> float:
|
||||
async def get_amount(self) -> float:
|
||||
"""Calculate the amount to risk per trade as a percentage of equity.
|
||||
|
||||
Keyword Args:
|
||||
risk (float): Percentage of account balance to risk per trade. Defaults to zero.
|
||||
|
||||
Returns:
|
||||
float: Amount to risk per trade
|
||||
"""
|
||||
await self.account.refresh()
|
||||
risk = risk or self.risk
|
||||
return self.account.equity * risk
|
||||
return self.account.equity * self.risk
|
||||
@@ -18,8 +18,8 @@ class Records:
|
||||
records_dir(Path): Path to directory containing record of placed trades, If not given takes the default
|
||||
from the config
|
||||
"""
|
||||
config: Config = Config()
|
||||
mt5: MetaTrader = MetaTrader()
|
||||
config: Config
|
||||
mt5: MetaTrader
|
||||
|
||||
def __init__(self, records_dir: Path = ''):
|
||||
"""Initialize the Records class. The main method of this class is update_records which you should call to update
|
||||
@@ -28,6 +28,8 @@ class Records:
|
||||
Keyword Args:
|
||||
records_dir (Path): Path to directory containing record of placed trades.
|
||||
"""
|
||||
self.config = Config()
|
||||
self.mt5 = MetaTrader()
|
||||
self.records_dir = records_dir or self.config.records_dir
|
||||
|
||||
async def get_records(self):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import asyncio
|
||||
import csv
|
||||
from logging import getLogger
|
||||
|
||||
@@ -16,7 +15,7 @@ class Result:
|
||||
config (Config): The configuration object
|
||||
name: Any desired name for the result file object
|
||||
"""
|
||||
config = Config()
|
||||
config: Config
|
||||
|
||||
def __init__(self, result: OrderSendResult, parameters: dict = None, name: str = ''):
|
||||
"""
|
||||
@@ -26,6 +25,7 @@ class Result:
|
||||
parameters:
|
||||
name:
|
||||
"""
|
||||
self.config = Config()
|
||||
self.parameters = parameters or {}
|
||||
self.result = result
|
||||
self.name = name or parameters.get('name', 'Trades')
|
||||
|
||||
@@ -209,7 +209,7 @@ class Sessions:
|
||||
await self.current_session.close() if self.current_session else ...
|
||||
current_session = self.find_next(now)
|
||||
secs = current_session.until() + 10
|
||||
print(f'sleeping for {secs} seconds until next {current_session} session')
|
||||
logger.info(f'sleeping for {secs} seconds until next {current_session} session')
|
||||
await sleep(secs)
|
||||
self.current_session = current_session
|
||||
await self.current_session.begin()
|
||||
+6
-11
@@ -7,7 +7,6 @@ from datetime import time as dtime
|
||||
|
||||
from .core.meta_trader import MetaTrader
|
||||
from .symbol import Symbol as _Symbol
|
||||
from .account import Account
|
||||
from .core import Config
|
||||
from .sessions import Sessions, Session
|
||||
|
||||
@@ -23,21 +22,15 @@ class Strategy(ABC):
|
||||
parameters (Dict): A dictionary of parameters for the strategy.
|
||||
sessions (Sessions): The sessions to use for the strategy.
|
||||
|
||||
Class Attributes:
|
||||
account (Account): Account instance.
|
||||
mt5 (MetaTrader): MetaTrader instance.
|
||||
config (Config): Config instance.
|
||||
|
||||
Notes:
|
||||
Define the name of a strategy as a class attribute. If not provided, the class name will be used as the name.
|
||||
"""
|
||||
name: str
|
||||
symbol: Symbol
|
||||
sessions: Sessions
|
||||
account = Account()
|
||||
mt5: MetaTrader()
|
||||
config = Config()
|
||||
_parameters = {}
|
||||
mt5: MetaTrader
|
||||
config: Config
|
||||
parameters = {}
|
||||
|
||||
def __init__(self, *, symbol: Symbol, params: dict = None, sessions: Sessions = None, name=''):
|
||||
"""Initiate the parameters dict and add name and symbol fields.
|
||||
@@ -47,12 +40,14 @@ class Strategy(ABC):
|
||||
symbol (Symbol): The Financial instrument
|
||||
params (Dict): Trading strategy parameters
|
||||
"""
|
||||
self.parameters = self._parameters | (params or {})
|
||||
self.parameters = self.parameters | (params or {})
|
||||
self.symbol = symbol
|
||||
self.name = name or self.__class__.__name__
|
||||
self.parameters["symbol"] = symbol.name
|
||||
self.parameters["name"] = self.name
|
||||
self.sessions = sessions or Sessions(Session(start=0, end=dtime(hour=23, minute=59, second=59)))
|
||||
self.config = Config()
|
||||
self.mt5 = MetaTrader()
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.name}({self.symbol!r})"
|
||||
|
||||
+10
-1
@@ -26,7 +26,16 @@ class Symbol(SymbolInfo):
|
||||
Make sure Symbol is always initialized with a name argument
|
||||
"""
|
||||
tick: Tick
|
||||
account = Account()
|
||||
account: Account
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize the Symbol object with the name of the financial instrument.
|
||||
|
||||
Args:
|
||||
name (str): Name of the financial instrument
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.account = Account()
|
||||
|
||||
@property
|
||||
def pip(self):
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import NamedTuple
|
||||
from logging import getLogger
|
||||
from .core.models import TerminalInfo
|
||||
|
||||
logger = getLogger()
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class Terminal(TerminalInfo):
|
||||
@@ -67,4 +67,4 @@ class Terminal(TerminalInfo):
|
||||
Returns:
|
||||
int: Total number of available symbols
|
||||
"""
|
||||
return await self.mt5.symbols_total()
|
||||
return await self.mt5.symbols_total()
|
||||
+9
-7
@@ -1,7 +1,6 @@
|
||||
"""Module for working with price ticks."""
|
||||
|
||||
from typing import TypeVar, Iterable
|
||||
import reprlib
|
||||
|
||||
from pandas import DataFrame, Series
|
||||
import pandas_ta as ta
|
||||
@@ -31,27 +30,30 @@ class Tick:
|
||||
ask: float
|
||||
last: float
|
||||
volume: float
|
||||
time_msc:float
|
||||
time_msc: float
|
||||
flags: float
|
||||
volume_real:float
|
||||
volume_real: float
|
||||
Index: int
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
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}
|
||||
|
||||
return ("%(class)s(Index=%(Index)s, time=%(time)s, bid=%(bid)s, ask=%(ask)s, last=%(last)s, volume=%(volume)s,"
|
||||
" mid=%(mid)s)") % {"class": self.__class__.__name__, "time": self.time, "bid": self.bid,
|
||||
"ask": self.ask, "last": self.last, "volume": self.volume, 'Index': self.Index}
|
||||
|
||||
def set_attributes(self, **kwargs):
|
||||
"""Set attributes from keyword arguments"""
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
_Ticks = TypeVar('_Ticks', bound='Ticks')
|
||||
|
||||
|
||||
class Ticks:
|
||||
"""Container data class for price ticks. Arrange in chronological order.
|
||||
Supports iteration, slicing and assignment
|
||||
@@ -164,4 +166,4 @@ class Ticks:
|
||||
None: If inplace is True
|
||||
"""
|
||||
res = self._data.rename(columns=kwargs, inplace=inplace)
|
||||
return res if inplace else self.__class__(data=res)
|
||||
return res if inplace else self.__class__(data=res)
|
||||
+16
-13
@@ -28,7 +28,7 @@ class Trader(ABC):
|
||||
Class Attributes:
|
||||
config (Config): Config instance.
|
||||
"""
|
||||
config = Config()
|
||||
config: Config
|
||||
|
||||
def __init__(self, *, symbol: Symbol, ram: RAM = None):
|
||||
"""Initializes the order object and RAM instance
|
||||
@@ -37,6 +37,7 @@ class Trader(ABC):
|
||||
symbol (Symbol): Financial instrument
|
||||
ram (RAM): Risk Assessment and Management instance
|
||||
"""
|
||||
self.config = Config()
|
||||
self.symbol = symbol
|
||||
self.order = Order(symbol=symbol.name)
|
||||
self.ram = ram or RAM()
|
||||
@@ -92,39 +93,41 @@ class Trader(ABC):
|
||||
"""
|
||||
check = await self.order.check()
|
||||
if check.retcode != 0:
|
||||
logger.warning(f"Symbol: {self.order.symbol}\nResult:\n"
|
||||
f"{dict_to_string(check.get_dict(include={'comment', 'retcode'}), multi=True)}")
|
||||
logger.warning(f"""Unable to place order for {self.symbol}\n
|
||||
{dict_to_string(check.get_dict(include={'comment', 'retcode'}) | check.request._asdict(), multi=True)}""")
|
||||
return False
|
||||
return True
|
||||
|
||||
async def send_order(self):
|
||||
"""Send the order to the broker."""
|
||||
parameters = self.parameters.copy()
|
||||
result = await self.order.send()
|
||||
if result.retcode != 10009:
|
||||
logger.warning(f"Symbol: {self.order.symbol}\nResult:\n"
|
||||
f"{dict_to_string(result.get_dict(include={'comment', 'retcode'}), multi=True)}")
|
||||
logger.warning(f"""Unable to place order for {self.symbol}\n
|
||||
{dict_to_string(result.get_dict(include={'comment', 'retcode'}) | result.request._asdict(),
|
||||
multi=True)}\n""")
|
||||
return
|
||||
logger.info(f"Symbol: {self.order.symbol}\nOrder: {dict_to_string(result.dict, multi=True)}\n")
|
||||
await self.record_trade(result, parameters)
|
||||
logger.info(f"""Placed Trade for {self.symbol}\n{dict_to_string(
|
||||
result.get_dict(exclude={'request', 'retcode_external', 'retcode', 'request_id'}), multi=True)}\n""")
|
||||
await self.record_trade(result, parameters=self.parameters.copy())
|
||||
|
||||
async def record_trade(self, result: OrderSendResult, parameters: dict):
|
||||
async def record_trade(self, result: OrderSendResult, parameters: dict = None, name: str = ''):
|
||||
"""Record the trade in a csv file.
|
||||
|
||||
Args:
|
||||
result (OrderSendResult): Result of the order send
|
||||
parameters: parameters of the trading strategy used to place the trade
|
||||
name: Name of the trading strategy
|
||||
"""
|
||||
if result.retcode != 10009 or not self.config.record_trades:
|
||||
return
|
||||
params = parameters
|
||||
params = parameters or self.parameters.copy()
|
||||
profit = await self.order.calc_profit()
|
||||
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)
|
||||
params["date"] = str(date.date())
|
||||
params["time"] = str(date.time())
|
||||
res = Result(result=result, parameters=params, name=name)
|
||||
await res.save_csv()
|
||||
|
||||
@abstractmethod
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
"""Utility functions for aiomql."""
|
||||
|
||||
|
||||
def dict_to_string(data: dict, multi=False) -> str:
|
||||
"""Convert a dict to a string. Use for logging.
|
||||
|
||||
@@ -11,4 +12,4 @@ def dict_to_string(data: dict, multi=False) -> str:
|
||||
str: The string representation of the dict.
|
||||
"""
|
||||
sep = '\n' if multi else ', '
|
||||
return f"{sep}".join(f"{key}: {value}\n" for key, value in data.items())
|
||||
return f"{sep}".join(f"{key}: {value}" for key, value in data.items())
|
||||
@@ -1,10 +0,0 @@
|
||||
from aiomql import MetaTrader
|
||||
import pytest
|
||||
|
||||
from .fixtures import *
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class BaseTest:
|
||||
""""""
|
||||
mt5 = MetaTrader()
|
||||
@@ -1,31 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
from aiomql import MetaTrader as mt5, Config
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_default_config():
|
||||
data = {"win_percentage": 0.90, "record_dir": "Trade Records"}
|
||||
obj = open('mt5.json', 'w')
|
||||
json.dump(data, obj)
|
||||
obj.close()
|
||||
yield
|
||||
os.remove('mt5.json')
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_config():
|
||||
data = {"win_percentage": 0.8, "record_dir": "Trade_Records"}
|
||||
obj = open('config.json', 'w')
|
||||
json.dump(data, obj)
|
||||
obj.close()
|
||||
yield
|
||||
os.remove('config.json')
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def init():
|
||||
config = Config(filename="test_config.json")
|
||||
mt5._initialize()
|
||||
mt5._login(login=config.account_number, password=config.password, server=config.server)
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"account_number": 160286827,
|
||||
"password": "TheN@me0fTheW!nd",
|
||||
"server": "ForexTimeFXTM-Demo01"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
from aiomql import config
|
||||
|
||||
from . import get_config, get_default_config
|
||||
|
||||
|
||||
def test_default_config_file(get_default_config):
|
||||
conf = config.Config()
|
||||
assert conf.win_percentage == 0.90
|
||||
|
||||
|
||||
def test_config_file_name(get_config):
|
||||
conf = config.Config(filename='config.json')
|
||||
assert conf.win_percentage == 0.8
|
||||
@@ -1,7 +0,0 @@
|
||||
from aiomql import TradeAction
|
||||
|
||||
|
||||
class TestConstants:
|
||||
def test_trade_action(self):
|
||||
assert TradeAction.DEAL == 1
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
from aiomql.symbol import Symbol
|
||||
|
||||
from . import *
|
||||
|
||||
|
||||
class TestSymbol(BaseTest):
|
||||
sym = Symbol(name="EURJPY")
|
||||
|
||||
async def test_init(self):
|
||||
await self.sym.init()
|
||||
assert self.sym.select is True
|
||||
@@ -1,22 +0,0 @@
|
||||
from . import *
|
||||
from aiomql import Terminal
|
||||
|
||||
|
||||
class TestTerminal(BaseTest):
|
||||
terminal = Terminal()
|
||||
async def test_version(self):
|
||||
res = await self.terminal.version
|
||||
assert len(res) == 3
|
||||
|
||||
async def test_info(self):
|
||||
res = await self.terminal.info()
|
||||
assert res.connected is True
|
||||
|
||||
async def test_error(self):
|
||||
res = await self.terminal.last_error()
|
||||
assert res.code == 1
|
||||
|
||||
async def test_symbols_get(self):
|
||||
res = await self.terminal.symbols_get()
|
||||
sym = next(res)
|
||||
assert isinstance(sym.name, str)
|
||||
@@ -1,91 +0,0 @@
|
||||
# from datetime import datetime
|
||||
# from collections import defaultdict
|
||||
# from pickle import HIGHEST_PROTOCOL
|
||||
# import _pickle as pickle
|
||||
# import lzma
|
||||
# import asyncio
|
||||
# from itertools import product
|
||||
# from typing import Iterable, TypeAlias
|
||||
#
|
||||
# from .meta_trader import MetaTrader
|
||||
# from .constants import TimeFrame
|
||||
# from .. import account, Account, Ticks, Symbol, Candles
|
||||
#
|
||||
# Rates: TypeAlias = dict[Symbol, dict[TimeFrame, Candles]]
|
||||
# PriceTicks: TypeAlias = dict[Symbol, Ticks]
|
||||
#
|
||||
#
|
||||
# class MetaTester(MetaTrader):
|
||||
|
||||
# def __init__(self, *, file=None, data: 'TestData' = None):
|
||||
# self.file = file
|
||||
#
|
||||
# @property
|
||||
# def data(self):
|
||||
# return TestData.load(self.file)
|
||||
#
|
||||
#
|
||||
# class TestData:
|
||||
# rates: Rates
|
||||
# ticks: PriceTicks
|
||||
# account: Account
|
||||
#
|
||||
# def __init__(self, symbols: Iterable[Symbol], timeframes: Iterable[TimeFrame], start: datetime, end: datetime, file: str):
|
||||
# self.symbols = symbols
|
||||
# self.timeframes = timeframes
|
||||
# self.start = start
|
||||
# self.end = end
|
||||
# self.file = file
|
||||
#
|
||||
# @property
|
||||
# async def _account(self) -> Account:
|
||||
# await account.refresh()
|
||||
# return account
|
||||
#
|
||||
# @property
|
||||
# async def _ticks(self) -> PriceTicks:
|
||||
# tasks = []
|
||||
# symbols = []
|
||||
# for symbol in self.symbols:
|
||||
# coro = symbol.copy_ticks_range(date_from=self.start, date_to=self.end)
|
||||
# symbols.append(symbol)
|
||||
# tasks.append(asyncio.create_task(coro))
|
||||
# ticks = await asyncio.gather(*tasks)
|
||||
# return {symbol: ticks for symbol, ticks in zip(symbols, ticks)}
|
||||
#
|
||||
# @property
|
||||
# async def _rates(self) -> Rates:
|
||||
# _data = {'tasks': [], 'symbols': [], 'timeframes': []}
|
||||
# args: Iterable[tuple[Symbol, TimeFrame]] = product(self.symbols, self.timeframes)
|
||||
# for symbol, timeframe in args:
|
||||
# coro = symbol.copy_rates_range(date_from=self.start, date_to=self.end, timeframe=timeframe)
|
||||
# _data['tasks'].append(asyncio.create_task(coro))
|
||||
# _data['symbols'].append(symbol)
|
||||
# _data['timeframes'].append(timeframe)
|
||||
# _data['rates'] = await asyncio.gather(*_data['tasks'])
|
||||
#
|
||||
# data = defaultdict(dict)
|
||||
# for rates, symbol, timeframe in zip(_data['rates'], _data['symbols'], _data['timeframes']):
|
||||
# data[symbol] |= {timeframe: rates}
|
||||
# return data
|
||||
#
|
||||
# async def copy_data(self):
|
||||
# self.rates, self.ticks, self.account = await asyncio.gather(self._rates, self._ticks, self._account)
|
||||
#
|
||||
# async def dumps(self):
|
||||
# return pickle.dumps(self, protocol=HIGHEST_PROTOCOL)
|
||||
#
|
||||
# async def dump(self):
|
||||
# await self.copy_data()
|
||||
# with lzma.open(self.file, 'wb') as fh:
|
||||
# pickle.dump(self, fh, protocol=HIGHEST_PROTOCOL)
|
||||
#
|
||||
# @classmethod
|
||||
# def load(cls, file) -> 'TestData':
|
||||
# with lzma.open(file, 'rb') as fh:
|
||||
# return pickle.load(fh)
|
||||
#
|
||||
# @classmethod
|
||||
# def loads(cls, obj):
|
||||
# return pickle.loads(obj)
|
||||
#
|
||||
Reference in New Issue
Block a user