头图

模板

import MetaTrader5 as mt5
import time
from dataclasses import dataclass
from typing import List, Dict, Optional
import numpy as np


@dataclass
class GridLevel:
    price: float
    level: int


@dataclass
class Position:
    ticket: int
    type: int  # 1 for buy, -1 for sell
    volume: float
    price: float
    sl: float
    tp: float
    profit: float
    order_type: int


class TradingCycle:
    def __init__(self, cycle_id: int, entry_direction: int, entry_level: GridLevel,
                 base_volume: float, volume_multipliers: List[float], max_volume: float, is_start: bool):
        self.cycle_id = cycle_id
        self.entry_direction = entry_direction  # 1 for long, -1 for short
        self.entry_level = entry_level
        self.positions: List[Position] = []
        self.reverse_count = 0  # 反向加仓次数

        # 交易量相关参数
        self.base_volume = base_volume  # 基础交易量
        self.volume_multipliers = volume_multipliers  # 反向加仓倍数列表
        self.max_volume = max_volume  # 最大总交易量

        self.lastPosPrice = 0.0  # 最后一次加仓价格
        self.is_start = is_start
        self.current_grid_level = entry_level.level  # 记录当前所在的网格层级

        # 使用GridTrader的类变量
        self.magic = GridTrader.BASE_MAGIC_NUMBER + cycle_id  # 为每个周期生成唯一的magic number
        self.reference_price = 0.0  # 添加参考价格属性
        self.is_active = True  # 添加标记表示周期是否活跃

    def calculate_next_volume(self) -> float:
        """计算下一次反向加仓的量"""
        # 使用实际成交的反向加仓次数来计算下一次的加仓量
        if self.reverse_count >= len(self.volume_multipliers):
            print(f"已达到最大反向加仓次数: {self.reverse_count}")
            return 0.0

        # 使用当前反向加仓次数作为索引,获取下一个倍数
        current_multiplier = self.volume_multipliers[self.reverse_count]
        next_volume = self.base_volume * current_multiplier
        print(f"计算得到的加仓量: {next_volume}")

        log_file = f"trades_{symbol}_{time.strftime('%Y%m%d')}.txt"
        with open(log_file, "a", encoding='utf-8') as f:
            f.write('--------------' + "\n")
            f.write(f"self.reverse_count: {self.reverse_count}" + "\n")

        return next_volume

    def has_active_trades(self) -> bool:
        """检查该交易周期是否还有活跃的交易"""
        # 检查持仓
        positions = mt5.positions_get(symbol=GridTrader.symbol, magic=self.magic)
        if positions:
            return True

        # 检查挂单
        orders = mt5.orders_get(symbol=GridTrader.symbol, magic=self.magic)
        if orders:
            return True

        return False


class GridTrader:
    # 添加类变量
    BASE_MAGIC_NUMBER = 234000  # 基础magic number
    symbol = None  # 确保symbol是类变量,这样TradingCycle可以访问

    def __init__(self, symbol: str, grid_size: float, start_price: float, end_price: float,
                 base_volume: float,
                 volume_multipliers: List[float],
                 max_volume: float,
                 max_cycles: int):
        self.symbol = symbol
        self.grid_size = grid_size
        self.base_volume = base_volume
        self.volume_multipliers = volume_multipliers
        self.max_volume = max_volume
        self.max_cycles = max_cycles

        if not self._initialize_mt5():
            raise Exception("MT5初始化失败")

        if not self._initialize_symbol():
            symbols = mt5.symbols_get()
            available_symbols = [s.name for s in symbols]
            print(f"可用的交易品种: {available_symbols}")
            raise Exception(f"无法初始化交易品种 {symbol}")

        self.grid_levels = self._initialize_grid_levels(start_price, end_price)
        self.trading_cycles: Dict[int, TradingCycle] = {}
        self.cycle_counter = 0
        self.last_order_price = None  # 添加这行来保存最后一个挂单价格

        # 初始化第一个交易周期
        self.initialize_first_cycle()

        # 获取交易品种信息
        self.symbol_info = mt5.symbol_info(self.symbol)
        if self.symbol_info is None:
            raise Exception(f"无法获取交易品种 {symbol} 的信息")

        # 保存价格精度
        self.digits = self.symbol_info.digits
        print(f"交易品种 {symbol} 的价格精度为: {self.digits} 位小数")

        GridTrader.symbol = symbol  # 添加为类变量

    def _initialize_symbol(self) -> bool:
        """初始化交易品种"""
        # 先检查品种是否存在
        symbol_info = mt5.symbol_info(self.symbol)
        if symbol_info is None:
            print(f"未能获取交易品种信息 {self.symbol}")
            return False

        # 选择交易品种
        selected = mt5.symbol_select(self.symbol, True)
        if not selected:
            print(f"未能选择交易品种 {self.symbol}")
            return False

        return True

    def _initialize_mt5(self) -> bool:
        """初始化MT5连接"""
        mt5_path = "C:\\Program Files\\MetaTrader 5 EXNESS\\terminal64.exe"
        login = 76681502
        password = "Zhj123456?"
        server = "Exness-MT5Trial5"

        if not mt5.initialize(path=mt5_path):
            print("初始化失败:", mt5.last_error())
            return False

        # 检查并启用自动交易
        terminal_info = mt5.terminal_info()
        if terminal_info is None:
            print("无法获取终端信息")
            return False

        if not terminal_info.trade_allowed:
            print("警告: 自动交易被禁用")
            print("请在MT5中执行以下操作:")
            print("1. 点击顶部菜单的 '工具' -> '选项'")
            print("2. 切换到 '专家顾问' 选项卡")
            print("3. 勾选 '启用自动交易' 选项")
            print("4. 勾选 '允许网络交易' 选项")
            print("5. 点击 '确定' 保存设置")
            return False

        if not mt5.login(login=login, password=password, server=server):
            print(f"登录失败: {mt5.last_error()}")
            mt5.shutdown()
            return False

        print(f"成功登录账户 {login}")
        print("自动交易已启用")
        return True

    def _initialize_grid_levels(self, start_price: float, end_price: float) -> List[GridLevel]:
        """初始化具体的价格网格线"""
        levels = []

        # 获取当前价格
        tick = mt5.symbol_info_tick(self.symbol)
        if tick is None:
            raise Exception("无法获取当前价格")
        current_market_price = (tick.bid + tick.ask) / 2

        # 计算总网格数
        total_levels = int((end_price - start_price) / self.grid_size)
        # 计算中心位置
        middle_level = total_levels // 2

        # 计算0层级的价格(当前价格)应该在哪个网格
        price_offset = current_market_price - start_price
        grid_offset = int(price_offset / self.grid_size)

        # 从0层级开始向两生成网格
        for i in range(total_levels + 1):
            level_num = i - grid_offset  # 使当前价格所在层级为0
            price = start_price + (i * self.grid_size)
            levels.append(GridLevel(price=price, level=level_num))

        print("\n初始化网格:")
        for level in levels:
            print(f"层级 {level.level}: 价格 {level.price:.2f}")
        print(f"\n当前市场价格: {current_market_price:.2f}")

        # 找到最接近当前价格的网格并打印
        closest_level = min(levels, key=lambda x: abs(x.price - current_market_price))
        print(f"最接近的网格层级: {closest_level.level}, 价格: {closest_level.price:.2f}")

        return levels

    def get_current_grid_level(self, price: float) -> GridLevel:
        """
        获取当前价格所在的网格层级

        Args:
            price: 当前价格

        Returns:
            GridLevel: 当前价格所在的网格层级对象
        """
        # 计算相对于起始价格的差值
        start_price = self.grid_levels[0].price
        price_diff = price - start_price

        # 计算所在的网格索引
        grid_index = int(price_diff / self.grid_size)

        # 确保索引在有效范围
        grid_index = max(0, min(grid_index, len(self.grid_levels) - 1))

        # 获取对应的网格层级对象
        grid_level = self.grid_levels[grid_index]

        # 打印调试信息
        print(f"当前价格: {price:.2f}")
        print(f"所在网格层级: {grid_level.level} "
              f"(价格区间: {grid_level.price:.2f} - "
              f"{grid_level.price + self.grid_size:.2f})")

        return grid_level

    def create_new_cycle(self, entry_level: GridLevel):
        """创建新的交易周期"""
        tick = mt5.symbol_info_tick(self.symbol)
        if tick is None:
            raise Exception("无法获取当前价格")
        current_price = (tick.bid + tick.ask) / 2

        # 根据当前价格相对于网格层级的位置决定交易方向
        # 如果价格高于网格层级价格,做多;如果价格低于网格层级价格,做空
        entry_direction = 1 if current_price > entry_level.price else -1

        self.reverse_count = 0
        # 创建新的交易周期
        self.cycle_counter += 1
        new_cycle = TradingCycle(
            cycle_id=self.cycle_counter,
            entry_direction=entry_direction,
            entry_level=entry_level,
            base_volume=self.base_volume,
            volume_multipliers=self.volume_multipliers,
            max_volume=self.max_volume,
            is_start=True
        )

        # 设置参考价格为当前价格
        new_cycle.reference_price = entry_level.price

        # 将新周期添加到交易周期字典中
        self.trading_cycles[self.cycle_counter] = new_cycle

        return new_cycle

    def initialize_first_cycle(self):
        """初始化第一个交易周期"""
        # 获取当前价格所在的网格层级
        tick = mt5.symbol_info_tick(self.symbol)
        if tick is None:
            raise Exception("无法获取当前价格")

        current_price = (tick.bid + tick.ask) / 2
        entry_level = self.get_current_grid_level(current_price)
        self.create_new_cycle(entry_level)

    def _round_price(self, price: float) -> float:
        """根据交易品种的精度对价格进行四舍五入"""
        return round(price, self.digits)

    def check_and_place_orders(self, cycle: TradingCycle, current_price: float):
        """检查并执行挂单或取消挂单"""
        # 获取当前网格层级
        # current_level = self.get_current_grid_level(current_price)
        current_level = self.get_current_grid_level(cycle.reference_price)

        # 检查是否有未完成的挂单
        orders = mt5.orders_get(symbol=self.symbol, magic=cycle.magic)

        positions = mt5.positions_get(symbol=self.symbol)
        if positions:
            return

        # 根据当前价格与参考价格的关系定操作
        if current_price > cycle.reference_price:
            # 价格高于参考价格,应该挂Stop Buy,取消Stop Sell
            should_place_buy = True
            # 取消所有stop sell挂单
            if orders:
                for order in orders:
                    if order.type == mt5.ORDER_TYPE_SELL_STOP:
                        cancel_request = {
                            "action": mt5.TRADE_ACTION_REMOVE,
                            "order": order.ticket,
                        }
                        result = mt5.order_send(cancel_request)
                        if result.retcode == mt5.TRADE_RETCODE_DONE:
                            print(f"成功取消Stop Sell单 ticket:{order.ticket}")
                        else:
                            print(f"取消Stop Sell单失败: {result.comment}")
        else:
            # 价格低于参考价格,应该挂Stop Sell,取消Stop Buy
            should_place_buy = False
            # 取消所有stop buy挂单
            if orders:
                for order in orders:
                    if order.type == mt5.ORDER_TYPE_BUY_STOP:
                        cancel_request = {
                            "action": mt5.TRADE_ACTION_REMOVE,
                            "order": order.ticket,
                        }
                        result = mt5.order_send(cancel_request)
                        if result.retcode == mt5.TRADE_RETCODE_DONE:
                            print(f"成功取消Stop Buy单 ticket:{order.ticket}")
                        else:
                            print(f"取消Stop Buy单失败: {result.comment}")

        # 计算挂单量
        order_volume = self.base_volume
        if order_volume <= 0:
            return

        # 计算挂单价格并按照正确的精度进行四舍五入
        if should_place_buy:
            order_type = mt5.ORDER_TYPE_BUY_STOP
            # Stop Buy价格设置在当前网格上方
            pending_order_price = self._round_price(current_level.price + self.grid_size)
        else:
            order_type = mt5.ORDER_TYPE_SELL_STOP
            # Stop Sell价格设置在当前网格下方
            pending_order_price = self._round_price(current_level.price - self.grid_size)

        # 检查是否已经存在相同方向的挂单
        if orders:
            for order in orders:
                if (should_place_buy and order.type == mt5.ORDER_TYPE_BUY_STOP) or \
                        (not should_place_buy and order.type == mt5.ORDER_TYPE_SELL_STOP):
                    print("已存在相同方向的挂单,���过")
                    return

        # 设置止损价格
        sl_distance = self.grid_size * 2  # 两个网格的距离
        if should_place_buy:
            sl_price = self._round_price(pending_order_price - sl_distance)  # 买单在下方设置止损
        else:
            sl_price = self._round_price(pending_order_price + sl_distance)  # 卖单在上方设置止损

        # 设置挂单
        request = {
            "action": mt5.TRADE_ACTION_PENDING,
            "symbol": self.symbol,
            "volume": order_volume,
            "type": order_type,
            "price": pending_order_price,  # 现在使用了正确精度的价格
            "sl": sl_price,  # 添加止损价格
            "tp": 0.0,  # 不设置止盈
            "deviation": 20,
            "magic": cycle.magic,
            "comment": f"Grid_{cycle.cycle_id}",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_FOK
        }

        # 发送交易请求
        result = mt5.order_send(request)
        if result.retcode == mt5.TRADE_RETCODE_DONE:
            print(f"挂单成功: {'Stop Buy' if should_place_buy else 'Stop Sell'} @ {pending_order_price}")
            print(f"止损价格设置在: {sl_price}")
            cycle.lastPosPrice = pending_order_price

            # 记录日志
            self.log_trade(
                cycle_id=cycle.cycle_id,
                action='PENDING',
                price=pending_order_price,
                volume=order_volume,
                direction='BUY' if should_place_buy else 'SELL',
                grid_level=current_level.level,
                order_type='STOP',
                ticket=result.order
            )
        else:
            print(f"挂单失败: {result.comment}")

    def check_and_reverse_trade(self, cycle: TradingCycle, current_price: float):
        """检查是否需要反向加仓"""
        # 获取当前持仓
        positions = mt5.positions_get(symbol=self.symbol, magic=cycle.magic)
        if not positions:
            return

        # 获取最后一个持仓
        last_position = None
        for pos in positions:
            if last_position is None or pos.time > last_position.time:
                last_position = pos

        if not last_position:
            return

        # 如果已经达到最大反向加仓次数,检查是否需要平仓
        if cycle.reverse_count >= len(cycle.volume_multipliers):
            # 计算价格移动一个网格的阈值
            grid_threshold = self.grid_size

            # 检查价格是否向不利方向移动一个网格
            should_close_all = False
            if last_position.type == mt5.POSITION_TYPE_BUY:
                # 如果是多单,检查价格是否下跌超过一个网格
                if current_price < last_position.price_open - grid_threshold:
                    should_close_all = True
            else:
                # 如果是空单,检查价格是否上涨超过一个网格
                if current_price > last_position.price_open + grid_threshold:
                    should_close_all = True

            # 如果需要平仓,关闭所有持仓
            if should_close_all:
                print(f"达到最大马丁次数且价格移动超过一个网格,准备平掉所有仓位")
                for pos in positions:
                    close_request = {
                        "action": mt5.TRADE_ACTION_DEAL,
                        "symbol": self.symbol,
                        "volume": pos.volume,
                        "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY else mt5.ORDER_TYPE_BUY,
                        "position": pos.ticket,
                        "price": mt5.symbol_info_tick(self.symbol).bid if pos.type == mt5.POSITION_TYPE_BUY
                        else mt5.symbol_info_tick(self.symbol).ask,
                        "deviation": 20,
                        "magic": cycle.magic,
                        "comment": "Max martingale reached - force close",
                        "type_time": mt5.ORDER_TIME_GTC,
                        "type_filling": mt5.ORDER_FILLING_FOK,
                    }

                    result = mt5.order_send(close_request)
                    if result.retcode == mt5.TRADE_RETCODE_DONE:
                        print(f"已平仓位 #{pos.ticket}, 盈亏: {pos.profit}")

                        # 记录日志
                        self.log_trade(
                            cycle_id=cycle.cycle_id,
                            action='CLOSE',
                            price=close_request["price"],
                            volume=pos.volume,
                            direction='SELL' if pos.type == mt5.POSITION_TYPE_BUY else 'BUY',
                            grid_level=self.get_current_grid_level(current_price).level,
                            order_type='MARKET',
                            ticket=pos.ticket
                        )
                    else:
                        print(f"平仓失败 #{pos.ticket}: {result.comment}")

                return  # 平仓后直接返回,不执行后续的反向加仓逻辑

        # 检��是否已经达到最大反向加仓次数
        if cycle.reverse_count >= len(cycle.volume_multipliers):
            return

        # 获取最后一个持仓
        last_position = None
        for pos in positions:
            if last_position is None or pos.time > last_position.time:
                last_position = pos

        if not last_position:
            return

        # 计算价格移动阈值(0.2个网格大小)
        # price_threshold = self.grid_size * 0.05
        price_threshold = self.grid_size

        # 检查是否有未完成的挂单
        pending_orders = mt5.orders_get(symbol=self.symbol, magic=cycle.magic)
        if pending_orders:
            return

        # 初始化变量
        order_type = None
        order_price = None

        # 根据最后持仓的方向判断反向加仓条件
        if last_position.type == mt5.POSITION_TYPE_BUY:
            # 如果是多单,价格下跌超过阈值时考虑反向做空
            # if current_price < last_position.price_open - price_threshold:
            #     # 在下一个网格价格挂反向空单
            #     grid_level = self.get_current_grid_level(last_position.price_open)
            #     order_price = self._round_price(grid_level.price - self.grid_size)
            #     order_type = mt5.ORDER_TYPE_SELL_STOP

            grid_level = self.get_current_grid_level(last_position.price_open)
            order_price = self._round_price(grid_level.price - self.grid_size)
            order_type = mt5.ORDER_TYPE_SELL_STOP
        else:
            # 如果是空单,价格上涨超过阈值时考虑反向做多
            # if current_price > last_position.price_open + price_threshold:
            #     # 在上一个网格价格挂反向多单
            #     grid_level = self.get_current_grid_level(last_position.price_open)
            #     order_price = self._round_price(grid_level.price + self.grid_size)
            #     order_type = mt5.ORDER_TYPE_BUY_STOP

            grid_level = self.get_current_grid_level(last_position.price_open)
            order_price = self._round_price(grid_level.price + self.grid_size)
            order_type = mt5.ORDER_TYPE_BUY_STOP

        # 计算反向加仓量
        next_volume = cycle.calculate_next_volume()
        if next_volume <= 0:
            return

        # 设置反向挂单
        request = {
            "action": mt5.TRADE_ACTION_PENDING,
            "symbol": self.symbol,
            "volume": next_volume,
            "type": order_type,
            "price": order_price,
            "deviation": 20,
            "magic": cycle.magic,
            "comment": f"Grid_{cycle.cycle_id}_Reverse_{cycle.reverse_count + 1}",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_FOK
        }

        # 发送交易请求
        try:
            log_file = f"trades_{self.symbol}_{time.strftime('%Y%m%d')}.txt"

            result = mt5.order_send(request)
            if result is None:
                print(f"反向加仓挂单失败: 未收到返回结果")
                print(f"MT5错误信息: {mt5.last_error()}")
                return

            if result.retcode == mt5.TRADE_RETCODE_DONE:
                print(f"反向加仓挂单成功: {'Stop Buy' if order_type == mt5.ORDER_TYPE_BUY_STOP else 'Stop Sell'} "
                      f"@ {order_price}, 数量: {next_volume}")
                cycle.reverse_count += 1
                cycle.lastPosPrice = order_price

                # 设置止损价格
                sl_distance = self.grid_size * 2  # 两个网格的距离
                if order_type == mt5.ORDER_TYPE_BUY_STOP:
                    sl_price = self._round_price(order_price - sl_distance)  # 买单在下方设置止损
                else:
                    sl_price = self._round_price(order_price + sl_distance)  # 卖单在上方设置止损

                # 修改挂单,添加止损
                modify_request = {
                    "action": mt5.TRADE_ACTION_MODIFY,
                    "order": result.order,
                    "price": order_price,
                    "sl": sl_price,
                    "tp": 0.0,  # 不设置止盈
                    "magic": cycle.magic
                }

                modify_result = mt5.order_send(modify_request)
                if modify_result.retcode == mt5.TRADE_RETCODE_DONE:
                    print(f"成功设置止损价格: {sl_price}")
                else:
                    print(f"设置止损失败: {modify_result.comment}")

                # 记录日志
                self.log_trade(
                    cycle_id=cycle.cycle_id,
                    action='PENDING',
                    price=order_price,
                    volume=next_volume,
                    direction='BUY' if order_type == mt5.ORDER_TYPE_BUY_STOP else 'SELL',
                    grid_level=self.get_current_grid_level(current_price).level,
                    order_type='STOP',
                    ticket=result.order
                )
            else:
                print(f"反向加仓挂单失败: {result.comment}, 错误代码: {result.retcode}")
                with open(log_file, "a", encoding='utf-8') as f:
                    tick = mt5.symbol_info_tick(self.symbol)
                    current_price = (tick.bid + tick.ask) / 2
                    f.write('-------11111111-------' + "\n")
                    f.write(f"order_price: {order_price}" + "\n")
                    f.write(f"current_price: {current_price}" + "\n")
                    f.write(f"tick.bid: {tick.bid}" + "\n")
                    f.write(f"tick.ask: {tick.ask}" + "\n")
                    f.write(f"request: {request}" + "\n")


        except Exception as e:
            print(f"发送反向加仓订单时生错误: {str(e)}")
            print(f"MT5错误信息: {mt5.last_error()}")

            tick = mt5.symbol_info_tick(self.symbol)
            current_price = (tick.bid + tick.ask) / 2

            with open(log_file, "a", encoding='utf-8') as f:
                f.write('------222222222--------' + "\n")
                f.write(f"order_price: {order_price}" + "\n")
                f.write(f"current_price: {current_price}" + "\n")
                f.write(f"tick.bid: {tick.bid}" + "\n")
                f.write(f"tick.ask: {tick.ask}" + "\n")
                f.write(f"request: {request}" + "\n")

    def adjust_stop_loss(self, cycle: TradingCycle, current_price: float):
        """检查并调整止损"""
        # 获取当前持仓
        positions = mt5.positions_get(symbol=self.symbol, magic=cycle.magic)
        if not positions:
            return

        # 获取最后一个持仓
        last_position = None
        for pos in positions:
            if last_position is None or pos.time > last_position.time:
                last_position = pos

        if not last_position:
            return




        # 检查价格是否向有利方向移动一个网格
        price_moved = False
        if last_position.type == mt5.POSITION_TYPE_BUY:
            price_moved = current_price > last_position.price_open + self.grid_size
        else:
            price_moved = current_price < last_position.price_open - self.grid_size

        if not price_moved:
            # 获取马丁最大的加仓仓位
            # if last_position.volume == self.base_volume * self.volume_multipliers[-1]:
            #     # 关闭所有亏损仓位
            #     for pos in positions:
            #         if (pos.type == mt5.POSITION_TYPE_BUY and pos.price_current < pos.price_open) or \
            #                 (pos.type == mt5.POSITION_TYPE_SELL and pos.price_current > pos.price_open):
            #             close_request = {
            #                 "action": mt5.TRADE_ACTION_DEAL,
            #                 "symbol": self.symbol,
            #                 "volume": pos.volume,
            #                 "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY else mt5.ORDER_TYPE_BUY,
            #                 "position": pos.ticket,
            #                 "price": mt5.symbol_info_tick(
            #                     self.symbol).bid if pos.type == mt5.POSITION_TYPE_BUY else mt5.symbol_info_tick(
            #                     self.symbol).ask,
            #                 "deviation": 20,
            #                 "magic": cycle.magic,
            #                 "comment": "Close losing position",
            #                 "type_time": mt5.ORDER_TIME_GTC,
            #                 "type_filling": mt5.ORDER_FILLING_FOK,
            #             }
            #             result = mt5.order_send(close_request)
            #             if result.retcode == mt5.TRADE_RETCODE_DONE:
            #                 print(f"已关闭亏损仓位 #{pos.ticket}, 盈亏: {pos.profit}")
            #
            #                 # 记录日志
            #                 self.log_trade(
            #                     cycle_id=cycle.cycle_id,
            #                     action='CLOSE',
            #                     # price=close_price,
            #                     volume=pos.volume,
            #                     direction='SELL' if pos.type == mt5.POSITION_TYPE_BUY else 'BUY',
            #                     grid_level=self.get_current_grid_level(current_price).level,
            #                     order_type='MARKET',
            #                     ticket=pos.ticket
            #                 )
            #             else:
            #                 print(f"关闭亏损仓位失败 #{pos.ticket}: {result.comment}")

            return

        # 计算新的止损价格
        new_sl = None
        if last_position.sl == 0:  # 首次设置止损
            if last_position.type == mt5.POSITION_TYPE_BUY:
                new_sl = last_position.price_open + (self.grid_size * 0.92)
                # new_sl = last_position.price_open + self.grid_size
            else:
                new_sl = last_position.price_open - (self.grid_size * 0.92)
                # new_sl = last_position.price_open - self.grid_size

            # 关闭所有亏损仓位
            # 以前在这里触发关闭

        else:  # 后续移动止损
            if last_position.type == mt5.POSITION_TYPE_BUY:
                # 计算新的止损价格
                # potential_sl = last_position.price_open + (current_price - last_position.price_open) * 0.9

                a = last_position.price_open + (current_price - last_position.price_open) * 0.9 - 0.1
                b = last_position.sl
                potential_sl = max(a, b)

                # 只有当新的止损价格高于当前止损价格时才更新
                if potential_sl > last_position.sl:
                    new_sl = potential_sl
            else:
                # 计算新的止损价格
                # potential_sl = last_position.price_open - abs(current_price - last_position.price_open) * 0.9

                a = last_position.price_open - abs(current_price - last_position.price_open) * 0.9 + 0.1
                b = last_position.sl
                potential_sl = min(a, b)

                # 只有当新的止损价格低于当前止损价格时才更新
                if potential_sl < last_position.sl:
                    new_sl = potential_sl

        # 如果需要更新止损
        if new_sl is not None:
            # 为所有盈利仓位设置新的止损
            for pos in positions:
                # 检查是否是盈利仓位
                is_profitable = (pos.type == mt5.POSITION_TYPE_BUY and pos.price_current > pos.price_open) or \
                                (pos.type == mt5.POSITION_TYPE_SELL and pos.price_current < pos.price_open)

                if is_profitable:
                    modify_request = {
                        "action": mt5.TRADE_ACTION_SLTP,
                        "symbol": self.symbol,
                        "position": pos.ticket,
                        "sl": self._round_price(new_sl),
                        # "tp": pos.tp,  # 保持原有止盈
                        "magic": cycle.magic
                    }

                    result = mt5.order_send(modify_request)
                    if result.retcode == mt5.TRADE_RETCODE_DONE:
                        print(f"已更新仓位 #{pos.ticket} 的止损到 {new_sl}")
                        # # 关闭所有亏损仓位
                        # for pos in positions:
                        #     if (pos.type == mt5.POSITION_TYPE_BUY and pos.price_current < pos.price_open) or \
                        #             (pos.type == mt5.POSITION_TYPE_SELL and pos.price_current > pos.price_open):
                        #         close_request = {
                        #             "action": mt5.TRADE_ACTION_DEAL,
                        #             "symbol": self.symbol,
                        #             "volume": pos.volume,
                        #             "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY else mt5.ORDER_TYPE_BUY,
                        #             "position": pos.ticket,
                        #             "price": mt5.symbol_info_tick(
                        #                 self.symbol).bid if pos.type == mt5.POSITION_TYPE_BUY else mt5.symbol_info_tick(
                        #                 self.symbol).ask,
                        #             "deviation": 20,
                        #             "magic": cycle.magic,
                        #             "comment": "Close losing position",
                        #             "type_time": mt5.ORDER_TIME_GTC,
                        #             "type_filling": mt5.ORDER_FILLING_FOK,
                        #         }
                        #         result = mt5.order_send(close_request)
                        #         if result.retcode == mt5.TRADE_RETCODE_DONE:
                        #             print(f"已关闭亏损仓位 #{pos.ticket}, 盈亏: {pos.profit}")
                        #
                        #             # 记录日志
                        #             self.log_trade(
                        #                 cycle_id=cycle.cycle_id,
                        #                 action='CLOSE',
                        #                 # price=close_price,
                        #                 volume=pos.volume,
                        #                 direction='SELL' if pos.type == mt5.POSITION_TYPE_BUY else 'BUY',
                        #                 grid_level=self.get_current_grid_level(current_price).level,
                        #                 order_type='MARKET',
                        #                 ticket=pos.ticket
                        #             )
                        #         else:
                        #             print(f"关闭亏损仓位失败 #{pos.ticket}: {result.comment}")
                    else:
                        print(f"更新止损失败 #{pos.ticket}: {result.comment}")

    def check_cycles_status(self):
        """检查所有交易周期的状态,关闭已完成的周期并创建新周期"""
        cycles_to_remove = []

        for cycle_id, cycle in self.trading_cycles.items():
            if not cycle.has_active_trades():
                print(f"交易周期 {cycle_id} 已结束")
                cycles_to_remove.append(cycle_id)

        # 移除已结束的周期
        for cycle_id in cycles_to_remove:
            del self.trading_cycles[cycle_id]

            print(f"已删除交易周期 {cycle_id}")

            # 获取当前价格所在的网格层级
            tick = mt5.symbol_info_tick(self.symbol)
            if tick is None:
                print("无法获取当前价格,跳过创建新周期")
                continue

            current_price = (tick.bid + tick.ask) / 2
            entry_level = self.get_current_grid_level(current_price)

            # 创建新的交易周期
            self.reverse_count = 0
            self.positions = []

            new_cycle = self.create_new_cycle(entry_level)
            print(f"已创建新的交易周期 {new_cycle.cycle_id}")

    def execute_trades(self):
        """执行交易主循环"""
        while True:
            try:
                tick = mt5.symbol_info_tick(self.symbol)
                if tick is None:
                    print("无法获取最新价格信息")
                    time.sleep(0.2)
                    continue

                current_price = (tick.bid + tick.ask) / 2

                # 检查交易周期状态
                self.check_cycles_status()

                # 检查每个交易周期
                for cycle_id, cycle in self.trading_cycles.items():
                    # 检查是否需要挂单或取消挂单
                    self.check_and_place_orders(cycle, current_price)

                    # 检查是否需要反向加仓
                    self.check_and_reverse_trade(cycle, current_price)

                    # 检查是否需要调整止损
                    self.adjust_stop_loss(cycle, current_price)

                time.sleep(0.2)  # 添加短暂延时避免过于频繁的检查

            except Exception as e:
                print(f"发生错误: {str(e)}")
                time.sleep(0.2)

    def shutdown(self):
        """安全关闭所有交易并停止程序"""
        print("\n开始安全关闭程序...")

        try:
            # 1. 先取消所有属于本程序的挂单
            orders = mt5.orders_get(symbol=self.symbol)
            if orders:
                print(f"\n取消挂单:")
                for order in orders:
                    # 检查是否是本程序的订单(通过magic number范围判断)
                    if self.BASE_MAGIC_NUMBER <= order.magic <= (self.BASE_MAGIC_NUMBER + self.max_cycles):
                        request = {
                            "action": mt5.TRADE_ACTION_REMOVE,
                            "order": order.ticket,
                            "comment": "shutdown"
                        }
                        # 尝试最多3次取消订单
                        for attempt in range(3):
                            result = mt5.order_send(request)
                            if result and result.retcode == mt5.TRADE_RETCODE_DONE:
                                print(f"成功取消挂单 #{order.ticket}")
                                break
                            else:
                                print(f"第{attempt + 1}次取消挂单 #{order.ticket} 失败: {mt5.last_error()}")
                                time.sleep(0.2)

            # 2. 关闭所有属于本程序的持仓
            positions = mt5.positions_get(symbol=self.symbol)
            if positions:
                print(f"\n平仓所有持仓:")
                for pos in positions:
                    # 检查是否是本程序的持仓
                    if self.BASE_MAGIC_NUMBER <= pos.magic <= (self.BASE_MAGIC_NUMBER + self.max_cycles):
                        # 获取最新市场价格
                        tick = mt5.symbol_info_tick(self.symbol)
                        if tick is None:
                            print(f"无法获取市场价格,使用当前持仓价格")
                            close_price = pos.price_current
                        else:
                            close_price = tick.bid if pos.type == mt5.POSITION_TYPE_BUY else tick.ask

                        request = {
                            "action": mt5.TRADE_ACTION_DEAL,
                            "symbol": self.symbol,
                            "volume": float(pos.volume),
                            "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY else mt5.ORDER_TYPE_BUY,
                            "position": pos.ticket,
                            "price": close_price,
                            "deviation": 100,  # 增加滑点以确保成交
                            "magic": pos.magic,
                            "comment": "shutdown",
                            "type_time": mt5.ORDER_TIME_GTC,
                            "type_filling": mt5.ORDER_FILLING_FOK,
                        }

                        # 尝试最多5次平仓
                        success = False
                        for attempt in range(5):
                            result = mt5.order_send(request)
                            if result and result.retcode == mt5.TRADE_RETCODE_DONE:
                                print(f"成功平仓 #{pos.ticket}, 盈亏: {pos.profit}")
                                success = True
                                break
                            else:
                                print(f"第{attempt + 1}次平仓尝试失败 #{pos.ticket}: {mt5.last_error()}")
                                time.sleep(0.2)  # 等待1秒后重试
                                # 重新获取市场价格
                                tick = mt5.symbol_info_tick(self.symbol)
                                if tick:
                                    request["price"] = tick.bid if pos.type == mt5.POSITION_TYPE_BUY else tick.ask

                        if not success:
                            print(f"警告: 无法平仓 #{pos.ticket}, 尝试使用市价单")
                            # 最后尝试使用市价单强制平仓
                            request["type_filling"] = mt5.ORDER_FILLING_IOC
                            request["deviation"] = 1000  # 增加更大的滑点
                            result = mt5.order_send(request)
                            if result and result.retcode == mt5.TRADE_RETCODE_DONE:
                                print(f"使用市价单成功平仓 #{pos.ticket}")
                            else:
                                print(f"警���: 市价单平仓失败 #{pos.ticket}: {mt5.last_error()}")

            # 3. 最终检查
            final_positions = mt5.positions_get(symbol=self.symbol)
            remaining_positions = [pos for pos in final_positions if
                                   self.BASE_MAGIC_NUMBER <= pos.magic <= (self.BASE_MAGIC_NUMBER + self.max_cycles)]

            if remaining_positions:
                print("\n警告: 仍有未能关闭的持仓:")
                for pos in remaining_positions:
                    print(f"Ticket: {pos.ticket}, Magic: {pos.magic}, Volume: {pos.volume}, "
                          f"Type: {'Buy' if pos.type == mt5.POSITION_TYPE_BUY else 'Sell'}")
            else:
                print("\n所有持仓已成功关闭")

            # 4. 清理交易周期
            self.trading_cycles.clear()
            self.cycle_counter = 0

            print("\n程序安全关闭完成")

        except Exception as e:
            print(f"关闭程序时发生错误: {str(e)}")
            print(f"MT5错误信息: {mt5.last_error()}")
        finally:
            # 确保关闭MT5连接
            mt5.shutdown()

    def log_trade(self, cycle_id: int, action: str, price: float, volume: float,
                  direction: str, grid_level: int, order_type: str, ticket: int = None):
        """
        记录交易日志

        Args:
            cycle_id: 交易周期ID
            action: 操作类型('OPEN', 'CLOSE', 'PENDING')
            price: 价格
            volume: 交易量
            direction: 交易方向('BUY', 'SELL')
            grid_level: 网格层级
            order_type: 订单类型('MARKET', 'STOP', 'LIMIT')
            ticket: 订单号(可选)
        """
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        log_entry = (f"{timestamp}, Cycle:{cycle_id}, {action}, {direction}, "
                     f"Price:{price:.5f}, Volume:{volume:.2f}, "    
                     f"GridLevel:{grid_level}, Type:{order_type}")
        if ticket:
            log_entry += f", Ticket:{ticket}"

        log_file = f"trades_{self.symbol}_{time.strftime('%Y%m%d')}.txt"
        try:
            with open(log_file, "a", encoding='utf-8') as f:
                f.write(log_entry + "\n")
        except Exception as e:
            print(f"写入日志失败: {str(e)}")


if __name__ == "__main__":
    trader = None
    try:

        # 先初始化MT5
        mt5_path = "C:\\Program Files\\MetaTrader 5 EXNESS\\terminal64.exe"
        login = 
        password = ""
        server = "Exness-MT5Trial5"
        symbol = "XAUUSDm"  # symbol="XAUUSDm",  BTCUSDm

        if not mt5.initialize(path=mt5_path):
            print("初始化失败:", mt5.last_error())
            raise Exception("MT5初始化失败")

        if not mt5.login(login=login, password=password, server=server):
            print(f"登录失败: {mt5.last_error()}")
            mt5.shutdown()
            raise Exception("MT5登录失败")

        # 确保可以获取交易品种的价格
        if not mt5.symbol_select(symbol, True):
            raise Exception(f"无法选择交易品种{symbol}")
        # 获取当前价格
        tick = mt5.symbol_info_tick(symbol)
        if tick is None:
            raise Exception(f"无法获取{symbol}的价格信息")

        current_price = (tick.bid + tick.ask) / 2
        grid_range = 1500  # 网格总范围为100美元
        half_range = grid_range / 2

        print(f"当前价格: {current_price}")
        print(f"网格范围: {current_price - half_range} - {current_price + half_range}")

        # 创建交易实例,以当前价格为中心创建网格
        trader = GridTrader(
            symbol=symbol,
            grid_size=1,  # 每1 一个网格
            start_price=current_price - half_range,
            end_price=current_price + half_range,
            base_volume=0.01,  # 基础仓位0.01手
            volume_multipliers=[3.0, 6.0, 12.0],  # 反向加仓倍数
            max_volume=100.0,  # 最大总交易量
            max_cycles=5  # 最大同时运行的交易周期数量
        )
        # 开始交易
        trader.execute_trades()

    except Exception as e:
        print(f"错误: {str(e)}")

    finally:
        # 确保程序结束时关闭MT5连接
        trader.shutdown()
        mt5.shutdown()




周周架构师
292 声望409 粉丝