Загрузка данных


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()