Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/nautilus metatrader adapter #32

Merged
merged 46 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
317ff8c
chore: update packages README
seekersoftec Aug 15, 2024
bcad4c0
chore: rename mt5linux as mt5api
seekersoftec Aug 15, 2024
7b66c89
add TODO to MT5 Dockerfile
seekersoftec Aug 15, 2024
755a704
fix: moved MT5 errors from commons
seekersoftec Aug 15, 2024
54c273e
fix: add mt5api terminal error types
seekersoftec Aug 15, 2024
c688df4
chore: updated .gitignore
seekersoftec Aug 15, 2024
048cb66
cleaned connection error logger
seekersoftec Aug 15, 2024
e767303
tested metatrader 5 ticks with nautilus trader
seekersoftec Aug 21, 2024
9090620
feat: Add VNC server and remote access
seekersoftec Aug 22, 2024
089e7e9
removed old Metatrader terminal file
seekersoftec Aug 22, 2024
75c6696
Configured Git LFS to manage MetaTrader 5 teminal files
seekersoftec Aug 22, 2024
b6aacbc
create script to automate mt5 docker terminal login
seekersoftec Aug 23, 2024
ebb3001
feat: enhance MetaTrader 5 container with auto-login and improved usa…
seekersoftec Aug 23, 2024
20fe117
feat: refactor MT5 decoder to support DClient
seekersoftec Aug 24, 2024
a46979e
feat: Use asyncio for message handling and streaming
seekersoftec Aug 24, 2024
99fdba0
Fix: Improve MT5 terminal readiness check and expose VNC port
seekersoftec Aug 24, 2024
410a8c2
fix: Pass login details to verification
seekersoftec Aug 26, 2024
2eaaa2f
fix: Add type hints to VNCMT5Client methods
seekersoftec Aug 26, 2024
aaf3606
fix: Improved robustness of auto login by adding error handling and r…
seekersoftec Aug 26, 2024
1425770
Chore: Update README disclaimer
seekersoftec Aug 30, 2024
9d08ee9
chore: add proposed logo images
seekersoftec Aug 31, 2024
ac9f72a
Fix: Ignore specific MetaTrader 5 IPC timeout error
seekersoftec Aug 31, 2024
1ee2b0e
chore: update packages README
seekersoftec Aug 15, 2024
01fb499
chore: rename mt5linux as mt5api
seekersoftec Aug 15, 2024
8de9083
add TODO to MT5 Dockerfile
seekersoftec Aug 15, 2024
f8a47c7
fix: moved MT5 errors from commons
seekersoftec Aug 15, 2024
a620454
fix: add mt5api terminal error types
seekersoftec Aug 15, 2024
f64b7ca
chore: updated .gitignore
seekersoftec Aug 15, 2024
b948c4a
cleaned connection error logger
seekersoftec Aug 15, 2024
217efd8
tested metatrader 5 ticks with nautilus trader
seekersoftec Aug 21, 2024
8ac5f41
feat: Add VNC server and remote access
seekersoftec Aug 22, 2024
c7cb888
removed old Metatrader terminal file
seekersoftec Aug 22, 2024
a6ae53e
Configured Git LFS to manage MetaTrader 5 teminal files
seekersoftec Aug 22, 2024
81ce1a4
create script to automate mt5 docker terminal login
seekersoftec Aug 23, 2024
f091453
feat: enhance MetaTrader 5 container with auto-login and improved usa…
seekersoftec Aug 23, 2024
46703f9
feat: refactor MT5 decoder to support DClient
seekersoftec Aug 24, 2024
8bdb4e9
feat: Use asyncio for message handling and streaming
seekersoftec Aug 24, 2024
7a5457e
Fix: Improve MT5 terminal readiness check and expose VNC port
seekersoftec Aug 24, 2024
53509d6
fix: Pass login details to verification
seekersoftec Aug 26, 2024
32a0612
fix: Add type hints to VNCMT5Client methods
seekersoftec Aug 26, 2024
40f2663
fix: Improved robustness of auto login by adding error handling and r…
seekersoftec Aug 26, 2024
8cd496d
Chore: Update README disclaimer
seekersoftec Aug 30, 2024
d73a03f
chore: add proposed logo images
seekersoftec Aug 31, 2024
e2db01d
Fix: Ignore specific MetaTrader 5 IPC timeout error
seekersoftec Aug 31, 2024
dfa8517
Merge remote-tracking branch 'refs/remotes/origin/feature/nautilus-me…
seekersoftec Sep 3, 2024
3f8efab
feat: add order execution client and market data client
seekersoftec Sep 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.tar.gz filter=lfs diff=lfs merge=lfs -text
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,5 @@ cython_debug/
.venv
env.yaml
data/
trading-analytics/
dump
wenv

*environment_ids.json
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"python.analysis.extraPaths": ["./packages/adapters"]
"python.analysis.extraPaths": [
"./packages/adapters",
"./packages/nautilus_mt5api"
],
"makefile.configureOnOpen": true
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ Access the MetaTrader 5 Terminal with a web browser using this Docker image: [ht

## Disclaimer

**Important Note:** TradeFlow is designed for educational purposes only. The developers take no responsibility for any financial losses incurred while using this software. We strongly recommend thoroughly understanding the inherent risks associated with trading before using any automated system.
**Important Note:** The developers take no responsibility for any financial losses incurred while using this software. We strongly recommend thoroughly understanding the inherent risks associated with trading before using any automated system.
Binary file added docs/images/logo-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 22 additions & 19 deletions infrastructure/MetaTrader5/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
FROM golang:1.14-buster AS novnc-build
# FROM golang:1.14-buster AS novnc-build

WORKDIR /src
# WORKDIR /src

RUN go mod init build && \
go get github.com/geek1011/[email protected] && \
go build -o /bin/easy-novnc github.com/geek1011/easy-novnc
# RUN go mod init build && \
# go get github.com/geek1011/[email protected] && \
# go build -o /bin/easy-novnc github.com/geek1011/easy-novnc

FROM tobix/pywine:3.9

# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------
LABEL org.opencontainers.image.source=https://github.com/FortesenseLabs/metatrader-terminal
LABEL org.opencontainers.image.description="Metatrader 5 Terminal RPYC API"
# ----------------------------------------------------------------------------
LABEL org.opencontainers.image.description="Metatrader 5 Terminal (with RPYC API)"
# ----------------------------------------------------------------------------------------------

# USER root
ENV DISPLAY :0
Expand All @@ -20,9 +20,16 @@ ENV DISPLAY :0
ENV HOME=/root
ENV SERVER_HOST='0.0.0.0'
ENV SERVER_PORT=18812
ENV VNC_DESKTOP_NAME="Metatrader5"
ENV VNC_GEOMETRY="1280x800"
ENV DRIVE_C=${WINEPREFIX}/drive_c
STOPSIGNAL SIGRTMIN+3

# MT5 Account Details
ENV MT5_ACCOUNT_NUMBER=''
ENV MT5_PASSWORD=''
ENV MT5_SERVER=''

# Install required packages
RUN apt-get update -y && \
apt-get install -y --no-install-recommends openbox tigervnc-standalone-server supervisor gosu && \
Expand All @@ -36,7 +43,7 @@ RUN apt-get update -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists

COPY --from=novnc-build /bin/easy-novnc /usr/local/bin/
# COPY --from=novnc-build /bin/easy-novnc /usr/local/bin/
COPY assets/menu.xml /etc/xdg/openbox/
COPY assets/supervisord.conf /etc/

Expand All @@ -49,17 +56,13 @@ RUN tar -xzf Metatrader-5.tar.gz -C "$DRIVE_C/" \
&& rm menu.xml supervisord.conf Metatrader-5.tar.gz

RUN wine pip install -r requirements.txt \
&& chmod +x run_server.sh
&& chmod +x run_server.sh xtigervnc.sh easy-novnc.sh

EXPOSE 18812 8000
# 9876 5900 | if tigervnc needs to be accessed use a proxy such as Caddy
EXPOSE 5900 18812
# Used for debugs:
# - tiger vnc server => 5900
# - easy-novnc => 8000

CMD ["supervisord"]

# https://www.digitalocean.com/community/tutorials/how-to-remotely-access-gui-applications-using-docker-and-caddy-on-debian-9
# https://github.com/oposs/tl-docker/
#
# add option to automatically connect to an instance
# /usr/local/bin/easy-novnc --addr :8080 --host localhost --port 5900 --no-url-password --novnc-params "resize=remote"
# Hide connection options from the main screen: --basic-ui
# https://github.com/pgaskin/easy-novnc
# TODO: upgrade to wine >= 8 as wine 7 is unstable and unsupported by MT5
Binary file modified infrastructure/MetaTrader5/assets/Metatrader-5.tar.gz
Binary file not shown.
282 changes: 282 additions & 0 deletions infrastructure/MetaTrader5/assets/auto_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
"""
VNC Client for MetaTrader 5 Docker Login

## Resources:
- https://github.com/sibson/vncdotool/blob/main/docs/library.rst
"""

import os
import time
from vncdotool import api
from dotenv import load_dotenv


class VNCMT5Client:
"""
A client to automate MetaTrader 5 login using VNC.

Attributes:
client (api.VNCDoToolClient): The VNC client connection object.
"""

def __init__(self, server_url: str, password: str = None, timeout: int = 10):
"""
Initialize the VNCMT5Client with the server URL and optional password.

Args:
server_url (str): The VNC server URL to connect to.
password (str): The password for the VNC server (default is None).
timeout (int): Timeout for the VNC client connection (default is 10 seconds).
"""
self.client = api.connect(server_url, password=password)
self.client.timeout = timeout

def clear_and_type_value(self, value: str, empty_field: bool = False, next_field_count: int = 1):
"""
Clears a form field, enters a new value, and optionally moves to the next field.

Args:
value (str): The string value to enter in the form field.
empty_field (bool): Whether to empty the field before entering the value (default is False).
next_field_count (int): The number of times to press 'Tab' to move to the next field (default is 1).
"""
if empty_field:
# Select all text and delete it
self.client.keyPress('ctrl-A')
self.client.keyPress('del')
time.sleep(0.1)

# Enter the new value with a slight delay between each character
for character in value:
self.client.keyPress(character)
time.sleep(0.1)

# Optionally, press Tab to move to the next field
for _ in range(next_field_count):
self.client.keyPress('tab')
time.sleep(0.1)

time.sleep(2)

def ping_mt_server(self, server: str):
"""
Ping MetaTrader's server by searching for the broker server in the list of company servers available.

Args:
server (str): The broker server name to search for.
"""
time.sleep(5)

# Click on the File tab
self.client.mouseMove(20, 22)
self.client.mousePress(1)
time.sleep(0.5)

# Click on Open an Account
self.client.mouseMove(100, 310)
self.client.mousePress(1)
time.sleep(0.5)

# Search for server in list of companies
self.client.mouseMove(350, 250)
self.client.mousePress(1)
time.sleep(0.5)

self.clear_and_type_value(server, empty_field=True)
self.client.keyPress('enter')
time.sleep(5)

# Close search dialog
self.client.mouseMove(930, 630)
self.client.mousePress(1)
time.sleep(0.5)

def login_to_mt5(self, login: str, password: str, server: str):
"""
Logs in to the MetaTrader 5 account.

Args:
login (str): The MetaTrader 5 account login number.
password (str): The MetaTrader 5 account password.
server (str): The MetaTrader 5 server name.
"""
# Probe server
self.ping_mt_server(server)
time.sleep(2)

# Click on the File tab
self.client.mouseMove(20, 22)
self.client.mousePress(1)
time.sleep(0.5)

# Click on Login to Trade Account
self.client.mouseMove(100, 380)
self.client.mousePress(1)

# Fill the Login form
self.clear_and_type_value(login)
# Move to the next field
self.clear_and_type_value(password, next_field_count=2)
# Move to the next field
self.clear_and_type_value(server, empty_field=True)

time.sleep(0.2)
self.client.keyPress('enter')
time.sleep(10)

def enable_algo_trading(self):
"""
Enables algorithmic trading on the MetaTrader 5 platform.
"""
# Enable Algo trading
self.client.mouseMove(890, 50)
self.client.mousePress(1)
time.sleep(0.2)

def open_journal_tab(self):
"""
Tries to open the Journal tab on MetaTrader 5 (optional).
"""
# Try to open Journal tab (optional)
self.client.mouseMove(690, 760)
self.client.mousePress(1)
time.sleep(0.2)

def capture_screenshot(self, file_name: str = 'screenshot.png'):
"""
Captures a screenshot of the current VNC screen.

Args:
file_name (str): The name of the file to save the screenshot (default is 'screenshot.png').
"""
try:
self.client.captureScreen(file_name)
except TimeoutError:
print('Timeout when capturing screen')

def disconnect(self):
"""
Disconnects the VNC client.
"""
if self.client is not None:
self.client.disconnect()
self.client = None

def _set_login_successful_env_var(self):
"""
Sets an environment variable to indicate a successful login.
"""
os.environ['LOGIN_SUCCESSFUL'] = 'true'

def verify_login(self, login: str, password: str, server: str) -> bool:
"""
Verifies if the MetaTrader 5 login was successful.

This method attempts to log in to the MetaTrader 5 server using the provided credentials.
If the login is unsuccessful, it raises an exception with the error details.

Args:
login (str): The MetaTrader 5 account login number.
password (str): The MetaTrader 5 account password.
server (str): The MetaTrader 5 server name.

Returns:
bool: True if the login is successful, False otherwise.

Raises:
Exception: If the login fails, with the error code and description.
"""
time.sleep(15)

import MetaTrader5 as mt5

# Attempt to establish a connection to the MetaTrader 5 terminal
is_connected = mt5.initialize(
"",
login=int(login),
password=password,
server=server,
timeout=120,
portable=True
)

if is_connected:
print(f"Login successful: {is_connected}")
self._set_login_successful_env_var()
return True
else:
error_code, error_description = mt5.last_error()

# Check for IPC timeout => -10005:
if int(error_code) != mt5.RES_E_INTERNAL_FAIL_TIMEOUT:
raise Exception(f"Login failed, error code = {error_code}, description = {error_description}")

# Probe server
self.ping_mt_server(server)
time.sleep(0.5)
self.verify_login(login, password, server)

def load_mt5_credentials():
"""
Loads environment variables and retrieves MetaTrader 5 credentials.

:return: A tuple containing the login, password, and server values.
"""
# Load environment variables
load_dotenv()

# Retrieve MetaTrader 5 credentials from environment variables
login = os.getenv('MT5_ACCOUNT_NUMBER')
password = os.getenv('MT5_PASSWORD')
server = os.getenv('MT5_SERVER')

return login, password, server

def main():
# Load and check MetaTrader 5 credentials
login, password, server = load_mt5_credentials()

# Ensure required environment variables are set
while not all([login, password, server]):
print("Required environment variables (MT5_ACCOUNT_NUMBER, MT5_PASSWORD, MT5_SERVER) are not set.")
time.sleep(5) # Wait for 5 seconds before checking again
# Reload credentials in case they are set during the loop
login, password, server = load_mt5_credentials()

VNC_SERVER_URL = "localhost"
VNC_SERVER_PASSWORD = None

# Create a VNCMT5Client instance
vnc_mt5_client = VNCMT5Client(server_url=VNC_SERVER_URL, password=VNC_SERVER_PASSWORD)

try:
# Log in to MetaTrader 5
vnc_mt5_client.login_to_mt5(login, password, server)

# Enable algorithmic trading
vnc_mt5_client.enable_algo_trading()

# Optionally open the Journal tab
vnc_mt5_client.open_journal_tab()

# Verify login
vnc_mt5_client.verify_login(login, password, server)

raise KeyboardInterrupt("Login attempt completed successfully.")
except Exception as e:
print(f"An error occurred: {e}")

finally:
# Disconnect the VNC client
vnc_mt5_client.disconnect()


if __name__ == "__main__":
main()


#
# There are two login approaches,
# - Only once: Where the file is utilized during the container startup.
# - API connect: Where the file is called ass some sort of api to login the account, off course we can use the existing rpyc server.
# https://chatgpt.com/c/46d9d791-9271-4122-b462-f248e2671e16
Loading
Loading