1
1
Derivar 0
réplica de https://github.com/Qoyyuum/Forex-MT5-Bot.git sincronizado 2026-02-23 07:53:17 +00:00
Forex-MT5-Bot/bot.py

264 linhas
8,8 KiB
Python

2021-09-25 11:41:14 +08:00
import logging
import logging.config
from dataclasses import dataclass
2021-10-25 19:11:47 +08:00
from datetime import datetime, timedelta
from typing import Any
2021-09-25 11:41:14 +08:00
import MetaTrader5 as mt5
import numpy as np
import pandas as pd
import config
logging.config.fileConfig("logging.conf")
2021-09-30 10:56:56 +08:00
logger = logging.getLogger("app" if config.DEBUG else "root")
2021-09-25 11:41:14 +08:00
@dataclass
class Client:
2021-10-25 19:11:47 +08:00
__slots__ = [
"account",
"password",
"server",
"pair",
"lot",
"timeframe",
"y_predict",
"stop_loss",
]
2021-09-25 11:41:14 +08:00
account: int
password: str
server: str
pair: str
lot: float
timeframe: np.ndarray
2021-09-30 10:56:56 +08:00
def login(self) -> bool:
2021-09-25 11:41:14 +08:00
"Client login"
if mt5.login(
login=self.account, password=self.password, server=self.server
):
2021-09-30 10:56:56 +08:00
logger.debug(
2021-09-25 11:41:14 +08:00
f"Logged in to {self.server} with account {self.account}"
)
return True
else:
logger.critical(
f"Failed to login to {self.server} with account {self.account}"
)
return False
2021-10-13 20:15:37 +08:00
def determine_signal(
self, open_price: float, predicted_price: float
2021-10-25 19:11:47 +08:00
) -> Any:
2021-10-13 20:15:37 +08:00
"""Determine the trading signal based on which price is higher or lower"""
if open_price > predicted_price:
2021-09-25 11:41:14 +08:00
logger.debug(
2021-10-13 20:15:37 +08:00
f"Sell at {open_price} with predicted take profit {predicted_price}"
2021-09-25 11:41:14 +08:00
)
2021-09-30 10:56:56 +08:00
self.stop_loss = self.get_stop_loss(
2021-10-13 20:15:37 +08:00
mt5.symbol_info_tick(self.pair).bid, predicted_price
2021-09-30 10:56:56 +08:00
)
2021-10-25 19:11:47 +08:00
return self.order("sell")
2021-10-13 20:15:37 +08:00
elif open_price < predicted_price:
2021-09-25 11:41:14 +08:00
logger.debug(
2021-10-13 20:15:37 +08:00
f"Buy at {open_price} with predicted take profit {predicted_price}"
2021-09-25 11:41:14 +08:00
)
2021-09-30 10:56:56 +08:00
self.stop_loss = self.get_stop_loss(
2021-10-13 20:15:37 +08:00
mt5.symbol_info_tick(self.pair).ask, predicted_price
2021-09-30 10:56:56 +08:00
)
2021-10-25 19:11:47 +08:00
return self.order("buy")
2021-09-30 10:56:56 +08:00
else:
logger.warn(
"Miraculously the open price and the predicted price are exactly the same. Not sure what to do"
)
2021-10-25 19:11:47 +08:00
return False
def build_dataset(
self, df_data: np.ndarray, lookup_data: int = 1
) -> tuple[Any, Any]:
"""Split a dataframe and split the dataset to train a model"""
from sklearn.model_selection import train_test_split
2021-09-30 10:56:56 +08:00
2021-10-25 19:11:47 +08:00
df_data["adjclose"] = df_data["close"].shift(
periods=-lookup_data, fill_value=0
)
df_x = df_data.drop(columns="adjclose")
x_train, _xtest, y_train, _ytest = train_test_split(
df_x,
df_data["adjclose"],
test_size=0.2,
random_state=42,
shuffle=True,
)
return x_train, y_train
def analyze_and_trade(
self, rates: np.ndarray = None, current_rates: np.ndarray = None
) -> None:
"""Analyze the past X rates and trade on the current open price. X rates is configured by BARS_TO_TRAIN."""
2021-10-13 20:15:37 +08:00
while rates is None:
rates = mt5.copy_rates_from_pos(
self.pair, self.timeframe, 1, config.BARS_TO_TRAIN
)
2021-10-25 19:11:47 +08:00
while current_rates is None:
current_rates = mt5.copy_rates_from_pos(
self.pair, self.timeframe, 0, 1
)
2021-10-13 20:15:37 +08:00
x_train, y_train = self.build_dataset(pd.DataFrame(rates))
model = self.get_model_to_predict(x_train, y_train)
2021-10-25 19:11:47 +08:00
current_open_price, to_predict = self.get_current_rate_to_predict(
current_rates
)
2021-10-13 20:15:37 +08:00
self.y_predict = model.predict(to_predict)
2021-10-25 19:11:47 +08:00
return self.determine_signal(
current_open_price, float(self.y_predict[0])
)
2021-10-13 20:15:37 +08:00
2021-09-30 10:56:56 +08:00
def get_stop_loss(
self, open_price: float, predicted_price: float
) -> float:
if open_price > predicted_price:
2021-10-25 19:11:47 +08:00
return open_price + ((open_price - predicted_price))
2021-09-30 10:56:56 +08:00
else:
2021-10-25 19:11:47 +08:00
return open_price - ((predicted_price - open_price))
2021-09-25 11:41:14 +08:00
2021-10-25 19:11:47 +08:00
def get_current_rate_to_predict(self, current_rate: np.ndarray = None):
2021-09-25 11:41:14 +08:00
"""
Get the current rate, and open price
"""
current_df = pd.DataFrame(current_rate)
current_open_price = current_df.iloc[0]["open"]
2021-10-25 19:11:47 +08:00
return current_open_price, current_df
2021-09-25 11:41:14 +08:00
def get_model_to_predict(self, x_train, y_train):
"""
Build a model to fit with the x and y training data set and fit.
Returns the built and learned model
2021-09-30 10:56:56 +08:00
TODO: Investigate and find out what's a better model if not better from scikit-learn's Linear Regression.
2021-09-25 11:41:14 +08:00
"""
from sklearn.linear_model import LinearRegression
return LinearRegression().fit(x_train, y_train)
2021-10-25 19:11:47 +08:00
def set_lot_size(self) -> float:
"""Get from History Orders. If the last History Order was a loss, double the lot size in the next trade."""
to_date = datetime.now()
from_date = to_date - timedelta(days=4)
logger.debug(f"!!!CHECKING HISTORY DEALS FOR {self.pair}!!!")
2021-10-25 19:11:47 +08:00
history_deals = mt5.history_deals_get(
from_date, to_date, group=self.pair
)
if history_deals is None:
logger.debug(
f"No history orders with this pair: {self.pair}. Error code = {mt5.last_error()}"
)
return self.lot
elif len(history_deals) > 0:
comment = history_deals[len(history_deals) - 1].comment
if "tp" in comment or "sl" not in comment:
return self.lot
traded_volume = history_deals[len(history_deals) - 1].volume
return traded_volume + traded_volume
else:
logger.debug(f"Something went wrong. Error: {mt5.last_error()}")
return self.lot
def order(self, signal: str) -> bool:
2021-09-30 10:56:56 +08:00
"""Depending on the param signal, this will place an order for BUY or SELL"""
2021-09-25 11:41:14 +08:00
symbol_info = mt5.symbol_info(self.pair)
if symbol_info is None:
logger.error(f"{self.pair} not found, cannot call order_check()")
mt5.shutdown()
quit()
if not symbol_info.visible:
logger.debug(f"{self.pair} is not visible, trying to switch on")
if not mt5.symbol_select(self.pair, True):
logger.error(f"symbol_select({self.pair}) failed, exit")
mt5.shutdown()
quit()
2021-09-30 10:56:56 +08:00
symbol_info_tick = mt5.symbol_info_tick(self.pair)
2021-09-25 11:41:14 +08:00
order_type, price = (
2021-09-30 10:56:56 +08:00
(mt5.ORDER_TYPE_SELL, symbol_info_tick.bid)
2021-09-25 11:41:14 +08:00
if signal == "sell"
2021-09-30 10:56:56 +08:00
else (mt5.ORDER_TYPE_BUY, symbol_info_tick.ask)
2021-09-25 11:41:14 +08:00
)
2021-10-25 19:11:47 +08:00
self.lot = self.set_lot_size()
2021-09-25 11:41:14 +08:00
self.raw_order(
action=mt5.TRADE_ACTION_DEAL,
symbol=self.pair,
volume=self.lot,
type=order_type,
price=price,
tp=float(self.y_predict[0]),
2021-10-25 19:11:47 +08:00
sl=float(self.stop_loss),
2021-09-25 11:41:14 +08:00
deviation=20,
2021-09-30 10:56:56 +08:00
magic=20210927,
comment=config.COMMENT,
2021-09-25 11:41:14 +08:00
type_time=mt5.ORDER_TIME_DAY,
type_filling=mt5.ORDER_FILLING_IOC,
)
logger.debug(
f"Order Sent : by {self.pair} {self.lot} lots at {price} with deviation 20 points"
)
2021-10-25 19:11:47 +08:00
return True
2021-09-25 11:41:14 +08:00
def raw_order(self, **kwargs):
2021-09-30 10:56:56 +08:00
"""Sends the order from kwargs returns the result dictionary"""
2021-09-25 11:41:14 +08:00
logger.debug(kwargs)
2021-09-30 10:56:56 +08:00
result = (
mt5.order_check(kwargs) if config.DEBUG else mt5.order_send(kwargs)
)
2021-09-25 11:41:14 +08:00
if result.retcode != mt5.TRADE_RETCODE_DONE:
2021-09-30 10:56:56 +08:00
logger.error(
f"Order Send Failed for {self.pair}, RETCODE = {result.retcode}"
)
2021-09-25 11:41:14 +08:00
else:
2021-09-30 10:56:56 +08:00
logger.info(f"Order send done. Result: {result}")
2021-10-25 19:11:47 +08:00
logger.debug(f"Result Type: {type(result)}")
2021-09-25 11:41:14 +08:00
return result
2021-09-30 10:56:56 +08:00
def check_existing_positions(self) -> bool:
2021-09-25 11:41:14 +08:00
"""
Returns if there's any existing positions.
This is to help keep a limit to the number of orders a specific pair can handle.
In this function, its to handle that there can only be 1 existing active order per symbol/pair.
"""
logger.debug(f"Checking pair: {self.pair}")
pos = mt5.positions_get(symbol=self.pair)
logger.debug(f"{pos}")
return pos is None or len(pos) == config.NO_CONCURRENT_TRADES
2021-09-25 11:41:14 +08:00
2021-09-30 10:56:56 +08:00
def main() -> None:
"""Start the bot"""
2021-09-25 11:41:14 +08:00
if not mt5.initialize():
logger.critical(f"MT5 Init failed, error code {mt5.last_error()}")
quit()
else:
for p in config.PAIRS:
c = Client(
config.ACCOUNT,
config.PASSWORD,
config.SERVER,
p,
config.LOT_SIZE,
config.TIMEFRAME,
)
if c.login() and c.check_existing_positions():
2021-10-25 19:11:47 +08:00
c.analyze_and_trade()
2021-09-25 11:41:14 +08:00
if __name__ == "__main__":
2021-09-25 15:59:30 +08:00
try:
main()
except (KeyboardInterrupt, SystemExit):
logger.critical("Manually quit the program")