模板
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()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。