Загрузка данных
import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import time
from datetime import datetime
# ====================== НАСТРОЙКИ ======================
SYMBOLS = ["EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCAD", "NZDUSD"]
TF_TREND = mt5.TIMEFRAME_H4 # глобальный тренд
TF_ENTRY = mt5.TIMEFRAME_H1 # вход
EMA_FAST = 50
EMA_SLOW = 200
ADX_PERIOD = 14
ADX_MIN = 25 # минимальная сила тренда
ATR_PERIOD = 14
SL_ATR_MULT = 1.8 # стоп = 1.8 * ATR
TP_RR = 2.2 # тейк = 2.2 риска
TRAIL_ATR_MULT = 1.3 # трейлинг
RISK_PERCENT = 1.0 # риск на сделку в %
MAGIC = 7772026
# =======================================================
def initialize():
if not mt5.initialize():
print("Ошибка MT5:", mt5.last_error())
return False
print("MT5 подключен | Бот запущен")
return True
def get_df(symbol, timeframe, bars=400):
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, bars)
if rates is None or len(rates) < 250:
return None
df = pd.DataFrame(rates)
df['time'] = pd.to_datetime(df['time'], unit='s')
df['ema50'] = df['close'].ewm(span=EMA_FAST, adjust=False).mean()
df['ema200'] = df['close'].ewm(span=EMA_SLOW, adjust=False).mean()
# ATR
high_low = df['high'] - df['low']
high_close = np.abs(df['high'] - df['close'].shift())
low_close = np.abs(df['low'] - df['close'].shift())
tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
df['atr'] = tr.rolling(ATR_PERIOD).mean()
# ADX (упрощённый)
plus_dm = df['high'].diff()
minus_dm = df['low'].diff() * -1
plus_dm[plus_dm < 0] = 0
minus_dm[minus_dm < 0] = 0
tr14 = tr.rolling(ADX_PERIOD).sum()
plus_di = 100 * (plus_dm.rolling(ADX_PERIOD).sum() / tr14)
minus_di = 100 * (minus_dm.rolling(ADX_PERIOD).sum() / tr14)
dx = 100 * np.abs(plus_di - minus_di) / (plus_di + minus_di)
df['adx'] = dx.rolling(ADX_PERIOD).mean()
return df
def get_trend_direction(symbol):
"""Определяем глобальный тренд по H4"""
df = get_df(symbol, TF_TREND)
if df is None:
return None
last = df.iloc[-1]
if last['close'] > last['ema200'] and last['ema50'] > last['ema200']:
return "bull"
elif last['close'] < last['ema200'] and last['ema50'] < last['ema200']:
return "bear"
return None
def check_entry_signal(symbol, direction):
"""Сигнал на H1 в сторону тренда"""
df = get_df(symbol, TF_ENTRY)
if df is None:
return None
last = df.iloc[-1]
prev = df.iloc[-2]
if last['adx'] < ADX_MIN:
return None # слабый тренд — пропускаем
# Бычий сигнал
if direction == "bull":
if prev['ema50'] < prev['ema200'] and last['ema50'] > last['ema200']:
return "buy"
# Медвежий сигнал
if direction == "bear":
if prev['ema50'] > prev['ema200'] and last['ema50'] < last['ema200']:
return "sell"
return None
def calculate_lot(symbol, sl_points):
"""Риск 1% от депозита"""
account = mt5.account_info()
if account is None:
return 0.01
balance = account.balance
risk_money = balance * (RISK_PERCENT / 100)
symbol_info = mt5.symbol_info(symbol)
if symbol_info is None:
return 0.01
tick_value = symbol_info.trade_tick_value
tick_size = symbol_info.trade_tick_size
if tick_size == 0:
return 0.01
point_value = tick_value / tick_size
lot = risk_money / (sl_points * point_value)
# Нормализация лота
lot = max(symbol_info.volume_min, min(lot, symbol_info.volume_max))
lot = round(lot / symbol_info.volume_step) * symbol_info.volume_step
return round(lot, 2)
def has_position(symbol):
positions = mt5.positions_get(symbol=symbol)
if not positions:
return False
for p in positions:
if p.magic == MAGIC:
return True
return False
def manage_positions():
"""Трейлинг + закрытие по тейку/стопу (уже стоит в ордере)"""
positions = mt5.positions_get()
if not positions:
return
for pos in positions:
if pos.magic != MAGIC:
continue
symbol = pos.symbol
df = get_df(symbol, TF_ENTRY, bars=50)
if df is None:
continue
atr = df['atr'].iloc[-1]
tick = mt5.symbol_info_tick(symbol)
if pos.type == mt5.ORDER_TYPE_BUY:
new_sl = tick.bid - atr * TRAIL_ATR_MULT
if new_sl > pos.sl and new_sl < tick.bid:
request = {
"action": mt5.TRADE_ACTION_SLTP,
"position": pos.ticket,
"sl": new_sl,
"tp": pos.tp
}
mt5.order_send(request)
elif pos.type == mt5.ORDER_TYPE_SELL:
new_sl = tick.ask + atr * TRAIL_ATR_MULT
if (pos.sl == 0 or new_sl < pos.sl) and new_sl > tick.ask:
request = {
"action": mt5.TRADE_ACTION_SLTP,
"position": pos.ticket,
"sl": new_sl,
"tp": pos.tp
}
mt5.order_send(request)
def open_trade(symbol, signal):
if has_position(symbol):
return
df = get_df(symbol, TF_ENTRY)
if df is None:
return
atr = df['atr'].iloc[-1]
symbol_info = mt5.symbol_info(symbol)
point = symbol_info.point
tick = mt5.symbol_info_tick(symbol)
if signal == "buy":
price = tick.ask
sl = price - atr * SL_ATR_MULT
tp = price + (price - sl) * TP_RR
order_type = mt5.ORDER_TYPE_BUY
else:
price = tick.bid
sl = price + atr * SL_ATR_MULT
tp = price - (sl - price) * TP_RR
order_type = mt5.ORDER_TYPE_SELL
sl_points = abs(price - sl) / point
lot = calculate_lot(symbol, sl_points)
request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": symbol,
"volume": lot,
"type": order_type,
"price": price,
"sl": sl,
"tp": tp,
"deviation": 20,
"magic": MAGIC,
"comment": f"TrendBot {signal}",
"type_time": mt5.ORDER_TIME_GTC,
"type_filling": mt5.ORDER_FILLING_IOC,
}
result = mt5.order_send(request)
if result.retcode == mt5.TRADE_RETCODE_DONE:
print(f"[{datetime.now().strftime('%H:%M:%S')}] {signal.upper()} {symbol} | лот {lot} | SL {sl:.5f} | TP {tp:.5f}")
else:
print(f"Ошибка {symbol}: {result.comment}")
def main():
if not initialize():
return
print("="*50)
print("Трендовый бот | H4 фильтр + H1 вход + ADX + ATR")
print("Риск 1% | RR 1:2.2 | Трейлинг")
print("="*50)
while True:
try:
manage_positions()
for symbol in SYMBOLS:
direction = get_trend_direction(symbol)
if direction is None:
continue
signal = check_entry_signal(symbol, direction)
if signal:
open_trade(symbol, signal)
time.sleep(45) # проверка каждые 45 сек
except Exception as e:
print("Ошибка:", e)
time.sleep(15)
if __name__ == "__main__":
main()