"""
EMA + Stochastic 混合策略自动化交易系统
============================================================
多货币对支持 | 实时信号监控 | 自动下单 | GUI界面
修正：仅在M15 K线收盘时检测信号，避免重复检测
============================================================
"""

import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import threading
import time
import json
import os
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from collections import deque

import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import tkinter.font as tkfont

# ================================================================
# 配置参数（基于优化结果）
# ================================================================

SYMBOL_PARAMS = {
    'EURUSD': {'ema_p': 15, 'ob': 70, 'os': 30, 'sl_m': 0.2, 'tp_m': 1.5,
               'pip': 0.0001, 'pip_val': 10.0, 'digits': 5, 'lot': 0.1},
    'GBPUSD': {'ema_p': 21, 'ob': 80, 'os': 30, 'sl_m': 0.4, 'tp_m': 2.0,
               'pip': 0.0001, 'pip_val': 10.0, 'digits': 5, 'lot': 0.1},
    'USDJPY': {'ema_p': 50, 'ob': 80, 'os': 30, 'sl_m': 0.2, 'tp_m': 1.5,
               'pip': 0.001, 'pip_val': 9.5, 'digits': 3, 'lot': 0.1},
    'USDCHF': {'ema_p': 50, 'ob': 80, 'os': 20, 'sl_m': 0.2, 'tp_m': 2.5,
               'pip': 0.0001, 'pip_val': 10.5, 'digits': 5, 'lot': 0.1},
    'USDCAD': {'ema_p': 15, 'ob': 75, 'os': 20, 'sl_m': 0.2, 'tp_m': 1.5,
               'pip': 0.0001, 'pip_val': 9.8, 'digits': 5, 'lot': 0.1},
    'AUDUSD': {'ema_p': 30, 'ob': 80, 'os': 20, 'sl_m': 0.2, 'tp_m': 2.5,
               'pip': 0.0001, 'pip_val': 10.0, 'digits': 5, 'lot': 0.1},
    'NZDUSD': {'ema_p': 50, 'ob': 75, 'os': 30, 'sl_m': 0.2, 'tp_m': 1.5,
               'pip': 0.0001, 'pip_val': 10.0, 'digits': 5, 'lot': 0.1},
}

# 系统配置
TIMEFRAME = mt5.TIMEFRAME_M15
MAX_BARS = 200  # 加载的K线数量
COOLDOWN_BARS = 8  # 信号冷却期（根K线）
MAGIC_NUMBER = 123456

# ================================================================
# 数据结构
# ================================================================

@dataclass
class Signal:
    """交易信号"""
    symbol: str
    direction: int  # 1=BUY, -1=SELL
    entry_price: float
    sl_price: float
    tp_price: float
    atr: float
    reason: str
    timestamp: datetime
    bar_time: datetime  # 触发信号的K线时间

@dataclass
class TradeRecord:
    """交易记录"""
    ticket: int
    symbol: str
    direction: int
    entry_price: float
    entry_time: datetime
    sl_price: float
    tp_price: float
    exit_price: Optional[float] = None
    exit_time: Optional[datetime] = None
    pnl: Optional[float] = None
    status: str = "open"  # open, closed

# ================================================================
# 策略引擎
# ================================================================

class StrategyEngine:
    """策略计算引擎"""
    
    def __init__(self, symbol: str, params: dict):
        self.symbol = symbol
        self.params = params
        self.df = None
        self.last_processed_bar_time = None  # 最后处理的K线时间
        self.cooldown_counter = 0
        
    def update_data(self, rates: np.ndarray) -> bool:
        """更新数据并计算指标"""
        if rates is None or len(rates) < 100:
            return False
            
        df = pd.DataFrame(rates)
        df['time'] = pd.to_datetime(df['time'], unit='s')
        
        # 计算EMA
        ema_p = self.params['ema_p']
        df[f'ema{ema_p}'] = df['close'].ewm(span=ema_p, adjust=False).mean().shift(1)
        
        # 计算ATR
        tr = np.maximum(df['high'] - df['low'],
            np.maximum(np.abs(df['high'] - df['close'].shift(1)),
                       np.abs(df['low'] - df['close'].shift(1))))
        df['atr14'] = pd.Series(tr).rolling(14).mean().shift(1)
        
        # 计算Stochastic (K5D3)
        k = 5
        low_min = df['low'].rolling(k).min().shift(1)
        high_max = df['high'].rolling(k).max().shift(1)
        rng = (high_max - low_min).replace(0, 1e-10)
        df['sk'] = 100 * (df['close'].shift(1) - low_min) / rng
        df['sd'] = df['sk'].rolling(3).mean()
        
        self.df = df
        return True
    
    def check_signal(self, current_bar_time: datetime) -> Optional[Signal]:
        """检查是否有交易信号（基于最新的完整K线）"""
        if self.df is None or len(self.df) < 60:
            return None
            
        # 检查是否已经处理过这根K线
        if self.last_processed_bar_time == current_bar_time:
            return None
            
        # 冷却期检查
        if self.cooldown_counter > 0:
            self.cooldown_counter -= 1
            self.last_processed_bar_time = current_bar_time
            return None
            
        # 获取最新的完整K线（倒数第二根）
        # 因为最新一根（索引-1）可能未完成
        i = len(self.df) - 2
        if i < 50:
            return None
            
        try:
            # 获取K线时间
            bar_time = self.df['time'].iloc[i]
            
            # 验证K线时间是否匹配
            if bar_time != current_bar_time:
                # 如果不匹配，尝试找到正确的K线
                for idx in range(len(self.df) - 1, -1, -1):
                    if self.df['time'].iloc[idx] == current_bar_time:
                        i = idx
                        break
                else:
                    return None
            
            atr = self.df['atr14'].iloc[i]
            if not (atr > 0 and atr == atr):
                return None
                
            sk = self.df['sk'].iloc[i]
            sk_prev = self.df['sk'].iloc[i-1]
            ema = self.df[f'ema{self.params["ema_p"]}'].iloc[i]
            close = self.df['close'].iloc[i]
            
            if not all(v == v for v in [sk, sk_prev, ema, close]):
                return None
                
            ob = self.params['ob']
            os = self.params['os']
            
            bull = close > ema
            bear = close < ema
            
            direction = 0
            reason = ""
            
            # 买入信号
            if bull and sk_prev < os and sk >= os:
                direction = 1
                reason = f"Stochastic向上穿越{os} (EMA多头, K={sk:.1f}, D={self.df['sd'].iloc[i]:.1f})"
            # 卖出信号
            elif bear and sk_prev > ob and sk <= ob:
                direction = -1
                reason = f"Stochastic向下穿越{ob} (EMA空头, K={sk:.1f}, D={self.df['sd'].iloc[i]:.1f})"
                
            if direction == 0:
                self.last_processed_bar_time = current_bar_time
                return None
                
            # 计算止损止盈
            sl_m = self.params['sl_m']
            tp_m = self.params['tp_m']
            pip = self.params['pip']
            
            # 入场价使用下一根K线的开盘价
            entry = self.df['open'].iloc[i+1]
            
            if direction == 1:
                sl = entry - sl_m * atr
                tp = entry + tp_m * atr
            else:
                sl = entry + sl_m * atr
                tp = entry - tp_m * atr
                
            # 精确到小数点后位数
            digits = self.params['digits']
            sl = round(sl, digits)
            tp = round(tp, digits)
            entry = round(entry, digits)
            
            signal = Signal(
                symbol=self.symbol,
                direction=direction,
                entry_price=entry,
                sl_price=sl,
                tp_price=tp,
                atr=atr,
                reason=reason,
                timestamp=datetime.now(),
                bar_time=current_bar_time
            )
            
            # 设置冷却期
            self.cooldown_counter = COOLDOWN_BARS
            self.last_processed_bar_time = current_bar_time
            
            return signal
            
        except Exception as e:
            return None
    
    def get_current_indicators(self, bar_offset: int = -2) -> dict:
        """获取当前指标值（用于日志）"""
        if self.df is None or len(self.df) < 5:
            return {}
            
        i = len(self.df) + bar_offset
        if i < 0 or i >= len(self.df):
            return {}
            
        try:
            return {
                'time': self.df['time'].iloc[i],
                'close': round(self.df['close'].iloc[i], self.params['digits']),
                'ema': round(self.df[f'ema{self.params["ema_p"]}'].iloc[i], self.params['digits']),
                'stoch_k': round(self.df['sk'].iloc[i], 1),
                'stoch_d': round(self.df['sd'].iloc[i], 1),
                'atr': round(self.df['atr14'].iloc[i], self.params['digits']),
            }
        except:
            return {}

# ================================================================
# 交易执行器
# ================================================================

class TradeExecutor:
    """交易执行器"""
    
    def __init__(self):
        self.open_positions = {}  # symbol -> ticket
        self.trade_history = deque(maxlen=100)
        
    def place_order(self, signal: Signal, magic: int) -> Tuple[bool, int, str]:
        """下订单"""
        try:
            symbol_info = mt5.symbol_info(signal.symbol)
            if symbol_info is None:
                return False, 0, f"无法获取{signal.symbol}信息"
                
            # 检查是否已有同方向持仓
            positions = mt5.positions_get(symbol=signal.symbol)
            if positions:
                for pos in positions:
                    if pos.magic == magic and pos.type == (0 if signal.direction == 1 else 1):
                        return False, 0, f"已有同方向持仓 (ticket: {pos.ticket})"
            
            # 准备订单
            order_type = mt5.ORDER_TYPE_BUY if signal.direction == 1 else mt5.ORDER_TYPE_SELL
            
            # 获取当前价格（用于滑点控制）
            tick = mt5.symbol_info_tick(signal.symbol)
            if tick is None:
                return False, 0, "无法获取当前价格"
                
            current_price = tick.ask if signal.direction == 1 else tick.bid
            
            # 检查价格偏差
            price_diff = abs(current_price - signal.entry_price) / signal.params.get('pip', 0.0001)
            if price_diff > 5:  # 偏差超过5点
                self.log(f"价格偏差过大: 信号价={signal.entry_price}, 当前价={current_price}")
                # 使用当前价格重新计算止损止盈
                signal.entry_price = round(current_price, signal.params['digits'])
                if signal.direction == 1:
                    signal.sl_price = round(signal.entry_price - signal.sl_m * signal.atr, signal.params['digits'])
                    signal.tp_price = round(signal.entry_price + signal.tp_m * signal.atr, signal.params['digits'])
                else:
                    signal.sl_price = round(signal.entry_price + signal.sl_m * signal.atr, signal.params['digits'])
                    signal.tp_price = round(signal.entry_price - signal.tp_m * signal.atr, signal.params['digits'])
            
            request = {
                "action": mt5.TRADE_ACTION_DEAL,
                "symbol": signal.symbol,
                "volume": SYMBOL_PARAMS[signal.symbol]['lot'],
                "type": order_type,
                "price": signal.entry_price,
                "sl": signal.sl_price,
                "tp": signal.tp_price,
                "deviation": 20,
                "magic": magic,
                "comment": f"EMA_Stoch_{'BUY' if signal.direction==1 else 'SELL'}",
                "type_time": mt5.ORDER_TIME_GTC,
                "type_filling": mt5.ORDER_FILLING_IOC,
            }
            
            # 发送订单
            result = mt5.order_send(request)
            
            if result.retcode != mt5.TRADE_RETCODE_DONE:
                return False, 0, f"下单失败: {result.comment} (retcode={result.retcode})"
                
            # 记录持仓
            self.open_positions[signal.symbol] = result.order
            
            record = TradeRecord(
                ticket=result.order,
                symbol=signal.symbol,
                direction=signal.direction,
                entry_price=signal.entry_price,
                entry_time=datetime.now(),
                sl_price=signal.sl_price,
                tp_price=signal.tp_price,
                status="open"
            )
            self.trade_history.append(record)
            
            return True, result.order, f"下单成功: {signal.symbol} {'BUY' if signal.direction==1 else 'SELL'} @ {signal.entry_price}, SL={signal.sl_price}, TP={signal.tp_price}"
            
        except Exception as e:
            return False, 0, f"下单异常: {str(e)}"
    
    def get_open_positions_info(self) -> List[dict]:
        """获取持仓信息"""
        positions = mt5.positions_get()
        if positions is None:
            return []
            
        result = []
        for pos in positions:
            if pos.magic == MAGIC_NUMBER:
                result.append({
                    'ticket': pos.ticket,
                    'symbol': pos.symbol,
                    'type': 'BUY' if pos.type == 0 else 'SELL',
                    'volume': pos.volume,
                    'price_open': pos.price_open,
                    'sl': pos.sl,
                    'tp': pos.tp,
                    'price_current': pos.price_current,
                    'profit': pos.profit,
                })
        return result
    
    def close_all_positions(self) -> bool:
        """平仓所有持仓"""
        positions = mt5.positions_get()
        if positions is None:
            return True
            
        success = True
        for pos in positions:
            if pos.magic == MAGIC_NUMBER:
                tick = mt5.symbol_info_tick(pos.symbol)
                if tick is None:
                    continue
                    
                order_type = mt5.ORDER_TYPE_SELL if pos.type == 0 else mt5.ORDER_TYPE_BUY
                price = tick.bid if order_type == mt5.ORDER_TYPE_SELL else tick.ask
                
                request = {
                    "action": mt5.TRADE_ACTION_DEAL,
                    "symbol": pos.symbol,
                    "volume": pos.volume,
                    "type": order_type,
                    "position": pos.ticket,
                    "price": price,
                    "deviation": 20,
                    "magic": MAGIC_NUMBER,
                    "comment": "close_all",
                    "type_time": mt5.ORDER_TIME_GTC,
                    "type_filling": mt5.ORDER_FILLING_IOC,
                }
                result = mt5.order_send(request)
                if result.retcode != mt5.TRADE_RETCODE_DONE:
                    success = False
        return success

# ================================================================
# GUI应用
# ================================================================

class TradingBotGUI:
    """交易机器人GUI"""
    
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("EMA+Stochastic 混合策略交易系统 v2.0 - M15 K线收盘检测")
        self.root.geometry("1500x850")
        
        self.running = False
        self.monitor_thread = None
        
        # 初始化组件
        self.engines = {}
        self.executor = TradeExecutor()
        self.last_bar_times = {}  # 记录每个货币对最后处理的K线时间
        
        # 设置UI
        self.setup_ui()
        
        # 绑定关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        
    def setup_ui(self):
        """设置界面"""
        # 样式配置
        style = ttk.Style()
        style.theme_use('clam')
        
        # 主框架
        main_frame = ttk.Frame(self.root, padding="5")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 控制面板
        control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding="10")
        control_frame.grid(row=0, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        
        self.start_btn = ttk.Button(control_frame, text="▶ 启动监控", command=self.start_monitoring)
        self.start_btn.grid(row=0, column=0, padx=5)
        
        self.stop_btn = ttk.Button(control_frame, text="⏹ 停止监控", command=self.stop_monitoring, state=tk.DISABLED)
        self.stop_btn.grid(row=0, column=1, padx=5)
        
        self.close_all_btn = ttk.Button(control_frame, text="🔒 全部平仓", command=self.close_all_positions)
        self.close_all_btn.grid(row=0, column=2, padx=5)
        
        # 状态标签
        self.status_label = ttk.Label(control_frame, text="状态: 未启动", foreground="red")
        self.status_label.grid(row=0, column=3, padx=20)
        
        # 倒计时标签
        self.countdown_label = ttk.Label(control_frame, text="距K线收盘: --秒", font=('Arial', 10, 'bold'), foreground="blue")
        self.countdown_label.grid(row=0, column=4, padx=20)
        
        # 当前K线时间标签
        self.bar_label = ttk.Label(control_frame, text="当前K线: --", font=('Arial', 9))
        self.bar_label.grid(row=0, column=5, padx=20)
        
        # 持仓区域
        positions_frame = ttk.LabelFrame(main_frame, text="📊 当前持仓", padding="10")
        positions_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        
        # 持仓表格
        columns = ('ticket', 'symbol', 'type', 'volume', 'open_price', 'sl', 'tp', 'current', 'profit')
        self.positions_tree = ttk.Treeview(positions_frame, columns=columns, show='headings', height=6)
        
        self.positions_tree.heading('ticket', text='订单号')
        self.positions_tree.heading('symbol', text='货币')
        self.positions_tree.heading('type', text='方向')
        self.positions_tree.heading('volume', text='手数')
        self.positions_tree.heading('open_price', text='开仓价')
        self.positions_tree.heading('sl', text='止损')
        self.positions_tree.heading('tp', text='止盈')
        self.positions_tree.heading('current', text='当前价')
        self.positions_tree.heading('profit', text='盈亏($)')
        
        self.positions_tree.column('ticket', width=80)
        self.positions_tree.column('symbol', width=70)
        self.positions_tree.column('type', width=50)
        self.positions_tree.column('volume', width=60)
        self.positions_tree.column('open_price', width=90)
        self.positions_tree.column('sl', width=90)
        self.positions_tree.column('tp', width=90)
        self.positions_tree.column('current', width=90)
        self.positions_tree.column('profit', width=80)
        
        scrollbar = ttk.Scrollbar(positions_frame, orient=tk.VERTICAL, command=self.positions_tree.yview)
        self.positions_tree.configure(yscrollcommand=scrollbar.set)
        
        self.positions_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        
        # 信号区域
        signals_frame = ttk.LabelFrame(main_frame, text="📡 历史信号", padding="10")
        signals_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        
        self.signals_text = scrolledtext.ScrolledText(signals_frame, height=12, width=70)
        self.signals_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 日志区域
        logs_frame = ttk.LabelFrame(main_frame, text="📝 系统日志", padding="10")
        logs_frame.grid(row=2, column=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        
        self.logs_text = scrolledtext.ScrolledText(logs_frame, height=12, width=50)
        self.logs_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 货币对状态区域
        status_frame = ttk.LabelFrame(main_frame, text="💹 货币对实时状态", padding="10")
        status_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 5))
        
        self.status_text = tk.Text(status_frame, height=10, font=('Consolas', 9))
        self.status_text.grid(row=0, column=0, sticky=(tk.W, tk.E))
        
        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        main_frame.columnconfigure(2, weight=1)
        main_frame.rowconfigure(2, weight=1)
        signals_frame.columnconfigure(0, weight=1)
        signals_frame.rowconfigure(0, weight=1)
        logs_frame.columnconfigure(0, weight=1)
        logs_frame.rowconfigure(0, weight=1)
        positions_frame.columnconfigure(0, weight=1)
        positions_frame.rowconfigure(0, weight=1)
        status_frame.columnconfigure(0, weight=1)
        
    def log(self, message: str, level: str = "INFO"):
        """添加日志"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        
        # 颜色标记
        color_tags = {
            "INFO": "black",
            "SUCCESS": "green",
            "WARNING": "orange",
            "ERROR": "red",
            "ALERT": "purple",
            "SIGNAL": "blue"
        }
        
        log_msg = f"[{timestamp}] [{level}] {message}\n"
        
        self.logs_text.insert(tk.END, log_msg)
        self.logs_text.see(tk.END)
        
        # 同时打印到控制台
        print(log_msg.strip())
        
    def add_signal(self, signal: Signal, executed: bool, msg: str = ""):
        """添加信号记录"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        dir_str = "🟢 BUY" if signal.direction == 1 else "🔴 SELL"
        exec_str = "✅ 已执行" if executed else "❌ 未执行"
        
        signal_msg = f"[{timestamp}] {signal.symbol} {dir_str} @ {signal.entry_price}\n"
        signal_msg += f"   触发K线: {signal.bar_time.strftime('%Y-%m-%d %H:%M')}\n"
        signal_msg += f"   止损: {signal.sl_price} | 止盈: {signal.tp_price} | ATR: {signal.atr:.5f}\n"
        signal_msg += f"   原因: {signal.reason}\n"
        signal_msg += f"   结果: {exec_str} {msg}\n"
        signal_msg += "-" * 60 + "\n"
        
        self.signals_text.insert(tk.END, signal_msg)
        self.signals_text.see(tk.END)
        
    def update_positions_display(self):
        """更新持仓显示"""
        # 清空现有显示
        for item in self.positions_tree.get_children():
            self.positions_tree.delete(item)
            
        # 获取持仓
        positions = self.executor.get_open_positions_info()
        
        for pos in positions:
            profit_color = "green" if pos['profit'] >= 0 else "red"
            values = (
                pos['ticket'],
                pos['symbol'],
                pos['type'],
                f"{pos['volume']:.2f}",
                f"{pos['price_open']:.5f}",
                f"{pos['sl']:.5f}" if pos['sl'] else "-",
                f"{pos['tp']:.5f}" if pos['tp'] else "-",
                f"{pos['price_current']:.5f}",
                f"{pos['profit']:.2f}"
            )
            item = self.positions_tree.insert('', tk.END, values=values)
            self.positions_tree.item(item, tags=(profit_color,))
        
        # 设置颜色标签
        self.positions_tree.tag_configure('green', foreground='green')
        self.positions_tree.tag_configure('red', foreground='red')
        
    def update_status_display(self):
        """更新货币对状态显示"""
        status_text = ""
        status_text += f"{'货币对':<10} {'K线时间':<20} {'收盘价':<12} {'EMA':<12} {'K':<8} {'D':<8} {'ATR':<12}\n"
        status_text += "-" * 90 + "\n"
        
        for symbol, engine in self.engines.items():
            indicators = engine.get_current_indicators(bar_offset=-2)  # 最新完整K线
            if indicators:
                bar_time = indicators['time'].strftime('%Y-%m-%d %H:%M')
                status_text += f"{symbol:<10} {bar_time:<20} {indicators['close']:<12.5f} "
                status_text += f"{indicators['ema']:<12.5f} {indicators['stoch_k']:<8.1f} "
                status_text += f"{indicators['stoch_d']:<8.1f} {indicators['atr']:<12.5f}\n"
            else:
                status_text += f"{symbol:<10} {'等待数据':<20}\n"
                
        self.status_text.delete(1.0, tk.END)
        self.status_text.insert(1.0, status_text)
        
    def get_current_bar_info(self) -> Tuple[datetime, datetime, int]:
        """获取当前K线信息"""
        now = datetime.now()
        # 计算当前15分钟K线的开始时间
        minutes = (now.minute // 15) * 15
        bar_start = now.replace(minute=minutes, second=0, microsecond=0)
        bar_end = bar_start + timedelta(minutes=15)
        seconds_remaining = int((bar_end - now).total_seconds())
        return bar_start, bar_end, seconds_remaining
    
    def get_last_completed_bar_time(self, symbol: str) -> Optional[datetime]:
        """获取最新已完成K线的时间"""
        rates = mt5.copy_rates_from_pos(symbol, TIMEFRAME, 0, 2)
        if rates is None or len(rates) < 2:
            return None
        # rates[1]是前一根（已完成）K线
        return pd.to_datetime(rates[1]['time'], unit='s')
    
    def check_all_symbols_on_bar_close(self):
        """在所有货币对的K线收盘时检查信号"""
        self.log("=" * 60, "INFO")
        self.log("执行K线收盘检测...", "INFO")
        
        signals_found = []
        
        for symbol, engine in self.engines.items():
            try:
                # 获取最新已完成K线的时间
                bar_time = self.get_last_completed_bar_time(symbol)
                
                if bar_time is None:
                    self.log(f"{symbol} 无法获取K线时间", "WARNING")
                    continue
                
                # 检查是否已经处理过这根K线
                last_processed = self.last_bar_times.get(symbol)
                
                if last_processed == bar_time:
                    self.log(f"{symbol} K线 {bar_time} 已处理过", "INFO")
                    continue
                
                self.log(f"{symbol} 新K线收盘: {bar_time}", "INFO")
                
                # 获取完整数据
                rates = mt5.copy_rates_from_pos(symbol, TIMEFRAME, 0, MAX_BARS)
                if rates is None or len(rates) < 100:
                    self.log(f"{symbol} 数据不足", "WARNING")
                    continue
                
                # 更新引擎数据
                if not engine.update_data(rates):
                    self.log(f"{symbol} 指标计算失败", "WARNING")
                    continue
                
                # 获取指标值（详细日志）
                indicators = engine.get_current_indicators(bar_offset=-2)
                self.log(f"{symbol} 指标: 收盘={indicators.get('close', 'N/A')}, "
                        f"EMA={indicators.get('ema', 'N/A')}, "
                        f"K={indicators.get('stoch_k', 'N/A')}, "
                        f"D={indicators.get('stoch_d', 'N/A')}, "
                        f"ATR={indicators.get('atr', 'N/A')}")
                
                # 检查信号
                signal = engine.check_signal(bar_time)
                
                if signal:
                    self.log(f"🔥 发现信号! {symbol} {'BUY' if signal.direction==1 else 'SELL'} @ {signal.entry_price}", "ALERT")
                    self.log(f"   信号详情: {signal.reason}", "SIGNAL")
                    signals_found.append(signal)
                else:
                    self.log(f"{symbol} 无信号", "INFO")
                    
                # 记录已处理的K线
                self.last_bar_times[symbol] = bar_time
                
            except Exception as e:
                self.log(f"{symbol} 检测异常: {str(e)}", "ERROR")
                import traceback
                traceback.print_exc()
        
        # 执行下单
        for signal in signals_found:
            success, ticket, msg = self.executor.place_order(signal, MAGIC_NUMBER)
            if success:
                self.log(f"✅ 订单已执行: {msg}", "SUCCESS")
            else:
                self.log(f"❌ 订单失败: {msg}", "ERROR")
            self.add_signal(signal, success, msg)
        
        # 更新显示
        self.update_positions_display()
        self.update_status_display()
        
        if signals_found:
            self.log(f"本次检测共发现 {len(signals_found)} 个信号", "SUCCESS")
        else:
            self.log("本次检测无信号", "INFO")
            
    def monitor_loop(self):
        """监控循环 - 在K线收盘时检测"""
        self.log("监控线程启动，将在每根M15 K线收盘时检测信号", "SUCCESS")
        
        last_check_bar = None
        
        while self.running:
            try:
                # 获取当前K线信息
                bar_start, bar_end, seconds_remaining = self.get_current_bar_info()
                
                # 更新倒计时显示
                self.countdown_label.config(text=f"距K线收盘: {seconds_remaining}秒")
                self.bar_label.config(text=f"当前K线: {bar_start.strftime('%H:%M')}-{bar_end.strftime('%H:%M')}")
                
                # 检查是否刚收盘（剩余0-3秒内）
                if seconds_remaining <= 3 and seconds_remaining >= 0:
                    current_bar_id = bar_start
                    if last_check_bar != current_bar_id:
                        self.log(f"K线收盘触发: {bar_start.strftime('%Y-%m-%d %H:%M')}")
                        self.check_all_symbols_on_bar_close()
                        last_check_bar = current_bar_id
                        
                        # 等待几秒避免重复触发
                        time.sleep(5)
                
                # 定期更新持仓显示（不依赖K线收盘）
                if int(time.time()) % 10 == 0:
                    self.update_positions_display()
                    self.update_status_display()
                
                time.sleep(1)
                
            except Exception as e:
                self.log(f"监控循环异常: {str(e)}", "ERROR")
                import traceback
                traceback.print_exc()
                time.sleep(5)
                
    def start_monitoring(self):
        """启动监控"""
        if not mt5.initialize():
            messagebox.showerror("错误", "MT5初始化失败，请确保MT5已启动")
            return
            
        # 登录检查
        account_info = mt5.account_info()
        if account_info is None:
            messagebox.showerror("错误", "请先在MT5中登录交易账户")
            mt5.shutdown()
            return
            
        self.log(f"MT5初始化成功，账户: {account_info.login}")
        self.log(f"账户类型: {'模拟账户' if account_info.trade_mode == 0 else '实盘账户'}")
        self.log(f"可用资金: ${account_info.balance:.2f}")
        
        # 初始化策略引擎
        for symbol, params in SYMBOL_PARAMS.items():
            self.engines[symbol] = StrategyEngine(symbol, params)
            self.last_bar_times[symbol] = None
            self.log(f"已加载 {symbol} 策略配置: EMA={params['ema_p']}, "
                    f"OB/OS={params['ob']}/{params['os']}, "
                    f"SL/TP={params['sl_m']}/{params['tp_m']} ATR, "
                    f"手数={params['lot']}")
        
        # 验证货币对可用性
        for symbol in SYMBOL_PARAMS.keys():
            symbol_info = mt5.symbol_info(symbol)
            if symbol_info is None:
                self.log(f"警告: {symbol} 不可用", "WARNING")
            else:
                self.log(f"{symbol} 可用: 点差={symbol_info.spread}点")
        
        self.running = True
        self.start_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)
        self.status_label.config(text="状态: 运行中 (等待K线收盘)", foreground="green")
        
        # 启动监控线程
        self.monitor_thread = threading.Thread(target=self.monitor_loop, daemon=True)
        self.monitor_thread.start()
        
        self.log("✅ 监控系统已启动，将在每根M15 K线收盘时检测信号")
        
    def stop_monitoring(self):
        """停止监控"""
        self.running = False
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
        self.status_label.config(text="状态: 已停止", foreground="red")
        self.countdown_label.config(text="距K线收盘: --秒")
        
        self.log("⏹ 监控系统已停止")
        
    def close_all_positions(self):
        """平仓所有持仓"""
        if messagebox.askyesno("确认", "确定要平掉所有持仓吗？"):
            self.log("正在平仓所有持仓...")
            if self.executor.close_all_positions():
                self.log("✅ 所有持仓已平仓")
                self.update_positions_display()
            else:
                self.log("❌ 平仓操作部分失败", "ERROR")
                
    def on_closing(self):
        """关闭程序"""
        if self.running:
            self.stop_monitoring()
            
        mt5.shutdown()
        self.root.destroy()
        
    def run(self):
        """运行程序"""
        self.root.mainloop()

# ================================================================
# 主程序
# ================================================================

def main():
    print("=" * 70)
    print("EMA+Stochastic 混合策略自动化交易系统 v2.0")
    print("=" * 70)
    print("\n核心特性:")
    print("  ✅ 仅在M15 K线收盘时检测信号（避免重复检测）")
    print("  ✅ 基于优化结果配置每个货币对独立参数")
    print("  ✅ 实时显示K线倒计时")
    print("  ✅ 详细日志包含所有指标计算过程")
    print("\n支持的货币对及参数:")
    print("-" * 70)
    for symbol, params in SYMBOL_PARAMS.items():
        print(f"  {symbol}: EMA={params['ema_p']:2}, OB/OS={params['ob']}/{params['os']}, "
              f"SL/TP={params['sl_m']}/{params['tp_m']} ATR, 手数={params['lot']}")
    print("-" * 70)
    print("\n启动GUI界面...")
    
    app = TradingBotGUI()
    app.run()

if __name__ == "__main__":
    main()