問題

请帮我分析下面这段代码,并给出修改意见。//+------------------------------------------------------------------+
//| SimpleGridEA with Martingale.mq5 |
//+------------------------------------------------------------------+
#property copyright "wei"
#property version "1.00"
#property strict

#include <Trade\Trade.mqh> // 交易操作函数
#include <Trade\PositionInfo.mqh> // 持仓信息函数
#include <Trade\HistoryOrderInfo.mqh> // 历史订单信息
#include <Trade\DealInfo.mqh> // 成交单信息

// 输入参数
input double LotSize = 0.1; // 基础交易手数
input double GridSize = 0.7; // 网格间距(美元)
input double BuyGridMultiplier = 1.0; // 买入网格倍数
input double SellGridMultiplier = 1.0; // 卖出网格倍数
input int MaxOrders = 200; // 最大订单数量
input double TakeProfit = 0.6; // 止盈点数(美元,实际盈利=设定值/10)
input double StopLoss = 100; // 止损点数(美元,实际亏损=设定值/10)

// 全局变量
double g_basePrice = 0;
datetime g_lastMessageTime = 0;
double g_currentLotSize = LotSize; // 当前手数
ulong g_lastOrderTicket = 0; // 最后订单号
bool g_isInProfit = false; // 是否处于盈利状态
int g_buyPrices[]; // 记录已开多单的整数价格
int g_sellPrices[]; // 记录已开空单的整数价格

//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 打印初始化信息
Print("=== EA初始化开始 ===");
Print("交易品种: ", _Symbol);
Print("点值: ", SymbolInfoDouble(_Symbol, SYMBOL_POINT));
Print("最小交易量: ", SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN));
Print("最大交易量: ", SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX));
Print("实际止盈: ",TakeProfit/10);Print("实际止损:", TakeProfit/10); Print("实际止损: ", StopLoss/10);

text
// 检查网格倍数设置 if(BuyGridMultiplier <= 0 || SellGridMultiplier <= 0) { Print("错误:网格倍数必须大于0"); return INIT_FAILED; } Print("买入网格倍数: ", BuyGridMultiplier); Print("卖出网格倍数: ", SellGridMultiplier); // 获取当前价格作为基准价格 g_basePrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); Print("初始基准价格: ", g_basePrice); // 检查交易设置 if(!CheckTradeSettings()) return INIT_FAILED; Print("=== EA初始化完成 ==="); return(INIT_SUCCEEDED);

}

//+------------------------------------------------------------------+
//| 检查交易设置 |
//+------------------------------------------------------------------+
bool CheckTradeSettings()
{
// 检查手数是否在允许范围内
double minVolume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxVolume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

text
if(LotSize < minVolume || LotSize > maxVolume) { Print("错误:手数设置不正确。允许范围:", minVolume, " - ", maxVolume); return false; } // 检查是否允许自动交易 if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Print("错误:未启用自动交易"); return false; } // 检查填充模式 uint filling = (uint)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); Print("交易品种填充模式: ", filling); if(filling == 0) { Print("错误:交易品种不支持任何填充模式"); return false; } return true;

}

//+------------------------------------------------------------------+
//| 在数组中搜索指定值 |
//+------------------------------------------------------------------+
int ArraySearch(const int &array[], int value)
{
int size = ArraySize(array);
for(int i = 0; i < size; i++)
{
if(array[i] == value)
return i;
}
return -1;
}

//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 每5秒打印一次市场信息
if(TimeCurrent() >= g_lastMessageTime + 5)
{
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double priceChange = MathAbs(currentPrice - g_basePrice);

text
Print("当前价格:", currentPrice); Print("基准价格:", g_basePrice); Print("价格变化:", priceChange, " (实际盈亏: $", priceChange/10, ")"); Print("持仓数量:", PositionsTotal()); Print("当前手数:", g_currentLotSize); Print("盈利状态:", g_isInProfit ? "是" : "否"); // 添加持仓监控 CheckPositions(); g_lastMessageTime = TimeCurrent(); } // 如果已达到最大订单数,不再开新仓 if(PositionsTotal() >= MaxOrders) return; double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // 根据盈利状态设置手数 if(g_isInProfit) { g_currentLotSize = LotSize * 1; // 盈利时使用4倍手数 } else { g_currentLotSize = LotSize; // 亏损时使用基础手数 } int currentPriceInt = (int)MathFloor(currentPrice); // 顺势网格交易逻辑 if(currentPrice >= g_basePrice + (GridSize * BuyGridMultiplier)) // 价格上涨超过网格间距 { // 检查当前整数价位是否已经开过多单 if(ArraySearch(g_buyPrices, currentPriceInt) == -1) { Print("=== 触发顺势买入信号 ==="); if(OpenBuyOrder()) { // 记录开仓价格 int size = ArraySize(g_buyPrices); ArrayResize(g_buyPrices, size + 1); g_buyPrices[size] = currentPriceInt; g_basePrice = currentPrice; Print("新基准价格:", g_basePrice); } } else { Print("当前价位", currentPriceInt, "已经开过多单,跳过"); } } else if(currentPrice <= g_basePrice - (GridSize * SellGridMultiplier)) // 价格下跌超过网格间距 { // 检查当前整数价位是否已经开过空单 if(ArraySearch(g_sellPrices, currentPriceInt) == -1) { Print("=== 触发顺势卖出信号 ==="); if(OpenSellOrder()) { // 记录开仓价格 int size = ArraySize(g_sellPrices); ArrayResize(g_sellPrices, size + 1); g_sellPrices[size] = currentPriceInt; g_basePrice = currentPrice; Print("新基准价格:", g_basePrice); } } else { Print("当前价位", currentPriceInt, "已经开过空单,跳过"); } } // 更新图表注释 UpdateComment(currentPrice);

}

//+------------------------------------------------------------------+
//| 检查持仓 |
//+------------------------------------------------------------------+
void CheckPositions()
{
if(PositionsTotal() == 0) return;

text
for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { double positionProfit = PositionGetDouble(POSITION_PROFIT); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); Print("持仓 #", ticket, " 类型=", positionType == POSITION_TYPE_BUY ? "买入" : "卖出", " 开仓价=", openPrice, " 当前盈亏=$", positionProfit); } }

}

//+------------------------------------------------------------------+
//| 处理止损 |
//+------------------------------------------------------------------+
void HandleStopLoss()
{
Print("=== 开始处理止损 ===");

text
g_isInProfit = false; Print("=== 止损处理完成 ===");

}

//+------------------------------------------------------------------+
//| 处理止盈 |
//+------------------------------------------------------------------+
void HandleTakeProfit()
{
Print("=== 开始处理止盈 ===");

text
g_isInProfit = true; Print("=== 止盈处理完成 ===");

}

//+------------------------------------------------------------------+
//| 开多仓 |
//+------------------------------------------------------------------+
bool OpenBuyOrder()
{
MqlTradeRequest request = {};
MqlTradeResult result = {};

text
// 获取交易品种的填充模式 uint filling = (uint)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = g_currentLotSize; request.type = ORDER_TYPE_BUY; request.price = ask; request.deviation = 10; request.magic = 123456; // 设置止盈止损 request.sl = NormalizeDouble(ask - StopLoss, _Digits); // 止损价格 request.tp = NormalizeDouble(ask + TakeProfit, _Digits); // 止盈价格 // 设置填充模式 if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) { request.type_filling = ORDER_FILLING_FOK; } else if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) { request.type_filling = ORDER_FILLING_IOC; } else { request.type_filling = ORDER_FILLING_RETURN; } Print("尝试开多仓:价格=", request.price, " 手数=", request.volume, " 止损=", request.sl, " (实际止损: $", StopLoss/10, ")", " 止盈=", request.tp, " (实际止盈: $", TakeProfit/10, ")", " 填充模式=", request.type_filling); if(!OrderSend(request, result)) { Print("订单发送失败,错误代码:", (string)GetLastError()); Print("错误描述:", GetErrorDescription(GetLastError())); return false; } g_lastOrderTicket = result.order; // 保存订单号 Print("订单发送成功!订单号:", result.order); return true;

}

//+------------------------------------------------------------------+
//| 开空仓 |
//+------------------------------------------------------------------+
bool OpenSellOrder()
{
MqlTradeRequest request = {};
MqlTradeResult result = {};

text
// 获取交易品种的填充模式 uint filling = (uint)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = g_currentLotSize; request.type = ORDER_TYPE_SELL; request.price = bid; request.deviation = 10; request.magic = 123456; // 设置止盈止损 request.sl = NormalizeDouble(bid + StopLoss, _Digits); // 止损价格 request.tp = NormalizeDouble(bid - TakeProfit, _Digits); // 止盈价格 // 设置填充模式 if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) { request.type_filling = ORDER_FILLING_FOK; } else if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) { request.type_filling = ORDER_FILLING_IOC; } else { request.type_filling = ORDER_FILLING_RETURN; } Print("尝试开空仓:价格=", request.price, " 手数=", request.volume, " 止损=", request.sl, " (实际止损: $", StopLoss/10, ")", " 止盈=", request.tp, " (实际止盈: $", TakeProfit/10, ")", " 填充模式=", request.type_filling); if(!OrderSend(request, result)) { Print("订单发送失败,错误代码:", (string)GetLastError()); Print("错误描述:", GetErrorDescription(GetLastError())); return false; } g_lastOrderTicket = result.order; // 保存订单号 Print("订单发送成功!订单号:", result.order); return true;

}

//+------------------------------------------------------------------+
//| 获取错误描述 |
//+------------------------------------------------------------------+
string GetErrorDescription(int error_code)
{
string error_string;

text
switch(error_code) { case 4756: error_string = "不支持的填充模式"; break; case 4752: error_string = "交易操作类型无效"; break; case 4751: error_string = "交易参数不正确"; break; case 4758: error_string = "交易品种不可用"; break; case 4755: error_string = "订单类型不正确"; break; default: error_string = "未知错误"; } return error_string;

}

//+------------------------------------------------------------------+
//| 交易事务处理 |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
const MqlTradeRequest& request,
const MqlTradeResult& result)
{
ENUM_TRADE_TRANSACTION_TYPE type = trans.type;

text
// 只关注成交事件 if(type != TRADE_TRANSACTION_DEAL_ADD) return; ulong dealTicket = trans.deal; if(!HistoryDealSelect(dealTicket)) return; ENUM_DEAL_ENTRY dealEntry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY); if(dealEntry != DEAL_ENTRY_OUT) return; double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); if(dealProfit < 0) // 止损 { HandleStopLoss(); } else if(dealProfit > 0) // 止盈 { HandleTakeProfit(); }

}

//+------------------------------------------------------------------+
//| 更新图表注释 |
//+------------------------------------------------------------------+
void UpdateComment(double currentPrice)
{
string buyPricesStr = "";
for(int i=0; i<ArraySize(g_buyPrices); i++) {
if(i > 0) buyPricesStr += ", ";
buyPricesStr += IntegerToString(g_buyPrices[i]);
}

text
string sellPricesStr = ""; for(int i=0; i<ArraySize(g_sellPrices); i++) { if(i > 0) sellPricesStr += ", "; sellPricesStr += IntegerToString(g_sellPrices[i]); } string commentText = "当前价格: " + DoubleToString(currentPrice, 2) + "\n" + "基准价格: " + DoubleToString(g_basePrice, 2) + "\n" + "基础网格间距: " + DoubleToString(GridSize, 2) + "\n" + "买入网格间距: " + DoubleToString(GridSize * BuyGridMultiplier, 2) + "\n" + "卖出网格间距: " + DoubleToString(GridSize * SellGridMultiplier, 2) + "\n" + "买入触发价格: " + DoubleToString(g_basePrice + (GridSize * BuyGridMultiplier), 2) + "\n" + "卖出触发价格: " + DoubleToString(g_basePrice - (GridSize * SellGridMultiplier), 2) + "\n" + "当前手数: " + DoubleToString(g_currentLotSize, 2) + "\n" + "已开多单价格: " + buyPricesStr + "\n" + "已开空单价格: " + sellPricesStr; Comment(commentText);

}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Comment(""); // 清除图表注释
ArrayFree(g_buyPrices); // 释放数组内存
ArrayFree(g_sellPrices); // 释放数组内存
Print("=== EA停止运行 ===");
Print("停止原因代码:", reason);
}

思考

Evaluating trading strategies

OK, let me see: the code stores price levels in dynamic arrays. The StopLoss and TakeProfit calculations seem off, lacking the symbol's TickValue.

Tackling trade dynamics

I’m digging into stopLoss and TakeProfit adjustments, noting they involve dealProfit, which includes commissions. This can lead to complexity or infinite growth if not carefully managed.

Clarifying the code logic

Interestingly enough, the code sets g_currentLotSize to 1, likely a leftover. It opens positions only when fewer than MaxOrders exist, considering symbol and open opposite positions.

Thinking through integer constraints

OK, let’s see—casting double prices to integers might miss exact values. Storing prices as doubles or with precise scaling seems wiser, especially with currency pairs having multiple decimals.

Translating profit metrics

To figure out real US profits or stopLoss/takeProfit points, using SymbolInfoDouble(...) and the contract size formula is crucial. Neglecting these might lead to incorrect price adjustments.

Highlighting critical areas

OK, I’m pulling together some key points: ensure accuracy in conversion logic, streamline error handling, reduce repetitive codes, switch to OnTimer() for periodic updates, and consider file-based logging instead of frequent print statements.

Tweaking lot sizes

Ah, I see. The Martingale strategy is suggested, but the code sets g_currentLotSize to 1 instead of 4. Adjusting this should lead to more accurate performance.

Aligning calculations

OK, addressing the discrepancy in StopLoss/TakeProfit calculations to match the actual symbol's scale is essential. Also, ensure arrays g_buyPrices and g_sellPrices handle decimal precision accurately.

Thought about Martingale trading strategy code,持续 12 秒

回答

下面是对代码的整体分析及建议修改点:

1. 止损和止盈的计算问题
目前代码中StopLossTakeProfit的计算方式略显混乱。

  • EA中注释显示“实际盈利=设定值/10”或“实际亏损=设定值/10”,但目前代码直接使用request.sl = ask - StopLoss;request.tp = ask + TakeProfit;(对于多单)这样的写法,似乎是将StopLossTakeProfit等值直接当成价格差,而非实际货币盈亏单位。

  • 如果StopLossTakeProfit的输入值是以货币(如USD)计价,需要将其转换为价格点差。这通常需要使用SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE)以及合约大小(SYMBOL_TRADE_CONTRACT_SIZE)等参数来计算。例如:

    cpp
    double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double contract_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); // 将美元止损金额换算成价格点差 double sl_points = (StopLoss/10) / (tick_value * contract_size); double tp_points = (TakeProfit/10) / (tick_value * contract_size); // 对多单 request.sl = NormalizeDouble(ask - sl_points * _Point, _Digits); request.tp = NormalizeDouble(ask + tp_points * _Point, _Digits);

    类似的,对空单也要相应调整。确保输入参数的含义(货币金额还是点差)和代码实际使用保持一致。

2. 网格价位存储方式
g_buyPricesg_sellPrices使用整数类型来存储价格(int g_buyPrices[]; int g_sellPrices[];),但实际价格通常带有小数(例如EURUSD可能有5位小数),使用(int)MathFloor(currentPrice)可能造成不精确。如果需要记录精确价格:

  • 建议将数组类型改为double[],确保记录精确价格点位。
  • 若一定需要存整数(可能是特定价位的概念),也要明确该整数化的逻辑(例如对价格乘以10000再取整)。

3. 关于手数加倍的逻辑
代码中注释写了“盈利时使用4倍手数” (// 盈利时使用4倍手数),但实际代码中只是 g_currentLotSize = LotSize * 1;并未实现加倍。需要根据策略修正此处逻辑。例如:

cpp
if(g_isInProfit) { g_currentLotSize = LotSize * 4; // 真正使用4倍手数 } else { g_currentLotSize = LotSize; }

若当前设计只是测试,请更新注释或逻辑确保一致。

4. 最大订单数判断与品种过滤
if(PositionsTotal() >= MaxOrders)就不再开新仓。但PositionsTotal()会返回所有品种的持仓数量(在某些情况下,如果账户多品种交易,这个判断不够精准)。建议使用PositionSelectByTicketPositionSelect(_Symbol)来统计当前品种的持仓数量,确保策略限制只针对本货币对的仓位数量。

例如:

cpp
int symbol_positions_count = 0; for (int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { if (PositionGetString(POSITION_SYMBOL) == _Symbol) symbol_positions_count++; } } if(symbol_positions_count >= MaxOrders) return;

5. 日志输出和性能问题
OnTick()函数中每5秒打印大量信息,这在实时运行时日志可能过于冗余。不仅影响性能,还会给日志分析带来困扰。建议:

  • 使用OnTimer()事件每5秒输出一次信息,而不是在OnTick()中计算时间间隔。
  • 或减少打印内容,仅在关键逻辑点打印。

6. 止盈止损处理逻辑
OnTradeTransaction()中通过dealProfit是否大于0或小于0来判断止盈/止损,这种判断方法相对简单粗暴。如果策略明确是通过价格触发StopLoss/TakeProfit,则更可靠的做法是通过对比HistoryDealGetDouble(dealTicket, DEAL_PRICE)与开仓价、预设SL/TP价位判断成交是因为何种原因触发。因为dealProfit受点差、手续费、滑点影响,并不能100%保证正负就一定是预期的止盈止损触发。

7. 填充模式检查
目前对填充模式的检查仅是简单判断,有些品种可能只支持ORDER_FILLING_FOKORDER_FILLING_IOC。已做判断算是不错,可以在遇到不支持的模式时给出更详细的用户提示,或在输入参数中提供可选模式设置。

8. 代码结构化与可维护性
当前代码逻辑较为集中在OnTick()中,建议将订单开仓逻辑、判断逻辑、打印逻辑分函数处理。例如:

  • CheckEntryConditions():检查是否需要下新单
  • OpenPositions():开仓函数(已经有OpenBuyOrder/OpenSellOrder,但可以再结构化)
  • ManagePositions():对已有仓位进行盈利/止损检查
  • PrintStatus():专门处理打印信息

这样可维护性更高。


总结修改建议:

  1. 止盈止损计算:明确止盈止损是以美元还是点数计价,并正确转换为价格点差。
  2. 价格存储精度:使用double数组存储价格,确保精确性。
  3. 手数倍增逻辑:修正g_isInProfit状态下的手数计算逻辑,与注释保持一致。
  4. 持仓限制:基于当前品种统计订单数,而非所有品种总数。
  5. 打印与性能优化:考虑使用OnTimer()或减少不必要的日志输出。
  6. 止盈止损判定逻辑:更精确地根据价格水平而非利润来判断是否触发了止盈/止损。
  7. 提高代码结构化程度:将逻辑模块化,提升可读性和易维护性。

通过上述修改,代码将更符合实际交易要求,更易维护并更易调试。

問題

好的,请按照你的修改意见,给我一份修改后的完整代码。

思考

Updating trading computations

I’m enhancing the code to compute stop loss and take profit using tick value and contract size, adding variables, adjusting data types, and incorporating the OnTimer event for printing.

Updating trading logic

I'm refining the code by integrating the OnTimer event for consistent printing, eliminating time checks from OnTick, enhancing StopLoss and TakeProfit calculations, and improving variable names and functionality.

Adjusting my approach

I’m finalizing the inclusion of a CountSymbolPositions function, unifying code references, updating variable types to double, fixing scaling logic, and refining StopLoss and TakeProfit calculations for consistency.

Implementing robust logic

I’m setting up global variables for stop loss and take profit offsets, calculating these values at OnInit, and using them in order functions while integrating the OnTimer event for periodic logging.

Thought about code optimization and improvements,持续 8 秒

回答

下面是修改后的完整代码示例。修改内容包括:

  1. 止盈止损计算方式调整
    使用tick_valuecontract_size将输入的美元止盈止损值转换为价格点差,并在开仓时正确设置止盈止损价格。

  2. 网格价位数组类型修改
    g_buyPricesg_sellPricesint改为double数组,以便记录准确的价格。

  3. 手数倍增逻辑修正
    g_isInProfit == true,则使用4倍基础手数。

  4. 持仓限制以本品种为准
    添加CountSymbolPositions()函数,仅对当前交易品种的持仓数量进行统计。

  5. 日志与性能优化
    使用OnTimer()事件每5秒输出一次状态信息,而非在OnTick()中反复检查时间。
    OnInit()中调用EventSetTimer(5)实现定时器。

  6. 止盈止损触发逻辑说明
    依旧通过OnTradeTransaction()中的dealProfit判断止盈止损,但在注释中提示可更精确的方法。

  7. 代码结构化与可维护性
    将打印与状态更新挪至OnTimer()OnTick()仅处理策略逻辑和开仓条件。

修改后的代码

mq5
//+------------------------------------------------------------------+ //| SimpleGridEA with Martingale Modified.mq5 | //+------------------------------------------------------------------+ #property copyright "wei" #property version "1.01" #property strict #include <Trade\Trade.mqh> // 交易操作函数 #include <Trade\PositionInfo.mqh> // 持仓信息函数 #include <Trade\HistoryOrderInfo.mqh> // 历史订单信息 #include <Trade\DealInfo.mqh> // 成交单信息 // 输入参数 input double LotSize = 0.1; // 基础交易手数 input double GridSize = 0.7; // 网格间距(美元) input double BuyGridMultiplier = 1.0; // 买入网格倍数 input double SellGridMultiplier = 1.0; // 卖出网格倍数 input int MaxOrders = 200; // 最大订单数量(针对当前品种) input double TakeProfit = 0.6; // 止盈美元值(实际价格差需计算) input double StopLoss = 100; // 止损美元值(实际价格差需计算) // 全局变量 double g_basePrice = 0; datetime g_lastMessageTime = 0; double g_currentLotSize = LotSize; // 当前手数 ulong g_lastOrderTicket = 0; // 最后订单号 bool g_isInProfit = false; // 是否处于盈利状态 double g_buyPrices[]; // 记录已开多单的价格 double g_sellPrices[]; // 记录已开空单的价格 // 止盈止损价格偏移(点数) double g_sl_points = 0.0; double g_tp_points = 0.0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Print("=== EA初始化开始 ==="); Print("交易品种: ", _Symbol); Print("点值: ", SymbolInfoDouble(_Symbol, SYMBOL_POINT)); Print("最小交易量: ", SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)); Print("最大交易量: ", SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)); double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double contract_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); // 将止盈止损从美元换算成点差数量(点数) // 注释中原先写实际止盈/止损 = 设定值/10,这里根据策略需要自行调整 // 假设TakeProfit和StopLoss是美元金额,则换算为点数: double tp_money = TakeProfit; double sl_money = StopLoss; g_tp_points = (tp_money / (tick_value * contract_size)) ; g_sl_points = (sl_money / (tick_value * contract_size)) ; Print("实际止盈: $", tp_money, ", 对应点差: ", g_tp_points); Print("实际止损: $", sl_money, ", 对应点差: ", g_sl_points); // 检查网格倍数设置 if(BuyGridMultiplier <= 0 || SellGridMultiplier <= 0) { Print("错误:网格倍数必须大于0"); return INIT_FAILED; } Print("买入网格倍数: ", BuyGridMultiplier); Print("卖出网格倍数: ", SellGridMultiplier); // 获取当前价格作为基准价格 g_basePrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); Print("初始基准价格: ", g_basePrice); // 检查交易设置 if(!CheckTradeSettings()) return INIT_FAILED; // 设置定时器,每5秒执行一次OnTimer() EventSetTimer(5); Print("=== EA初始化完成 ==="); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); // 清除图表注释 ArrayFree(g_buyPrices); // 释放数组内存 ArrayFree(g_sellPrices); // 释放数组内存 EventKillTimer(); // 取消定时器 Print("=== EA停止运行 ==="); Print("停止原因代码:", reason); } //+------------------------------------------------------------------+ //| 定时器函数: 每5秒打印状态 | //+------------------------------------------------------------------+ void OnTimer() { double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); double priceChange = MathAbs(currentPrice - g_basePrice); Print("当前价格:", currentPrice); Print("基准价格:", g_basePrice); Print("价格变化:", priceChange, " (实际盈亏估计: $", priceChange/10, ")"); // 实际盈亏的计算需要根据策略定义,这里沿用原注释 Print("当前品种持仓数量:", CountSymbolPositions()); Print("当前手数:", g_currentLotSize); Print("盈利状态:", g_isInProfit ? "是" : "否"); // 添加持仓监控 CheckPositions(); UpdateComment(currentPrice); } //+------------------------------------------------------------------+ //| 检查交易设置 | //+------------------------------------------------------------------+ bool CheckTradeSettings() { double minVolume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxVolume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); if(LotSize < minVolume || LotSize > maxVolume) { Print("错误:手数设置不正确。允许范围:", minVolume, " - ", maxVolume); return false; } // 检查是否允许自动交易 if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Print("错误:未启用自动交易"); return false; } // 检查填充模式 uint filling = (uint)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); Print("交易品种填充模式: ", filling); if(filling == 0) { Print("错误:交易品种不支持任何填充模式"); return false; } return true; } //+------------------------------------------------------------------+ //| 检查持仓 | //+------------------------------------------------------------------+ void CheckPositions() { int total = PositionsTotal(); for(int i = total - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; // 只关注本品种 double positionProfit = PositionGetDouble(POSITION_PROFIT); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); Print("持仓 #", ticket, " 类型=", positionType == POSITION_TYPE_BUY ? "买入" : "卖出", " 开仓价=", openPrice, " 当前盈亏=$", positionProfit); } } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // 如果已达到最大订单数,不再开新仓 if(CountSymbolPositions() >= MaxOrders) return; double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // 根据盈利状态设置手数(真正使用4倍手数) if(g_isInProfit) { g_currentLotSize = LotSize * 4; } else { g_currentLotSize = LotSize; } double buyTrigger = g_basePrice + (GridSize * BuyGridMultiplier); double sellTrigger = g_basePrice - (GridSize * SellGridMultiplier); // 顺势网格交易逻辑 if(currentPrice >= buyTrigger) // 价格上涨超过网格间距 { // 检查当前价位是否已开过多单 if(ArraySearch(g_buyPrices, currentPrice) == -1) { Print("=== 触发顺势买入信号 ==="); if(OpenBuyOrder()) { // 记录开仓价格 int size = ArraySize(g_buyPrices); ArrayResize(g_buyPrices, size + 1); g_buyPrices[size] = currentPrice; g_basePrice = currentPrice; Print("新基准价格:", g_basePrice); } } else { Print("当前价位", DoubleToString(currentPrice, _Digits), "已经开过多单,跳过"); } } else if(currentPrice <= sellTrigger) // 价格下跌超过网格间距 { // 检查当前价位是否已开过空单 if(ArraySearch(g_sellPrices, currentPrice) == -1) { Print("=== 触发顺势卖出信号 ==="); if(OpenSellOrder()) { int size = ArraySize(g_sellPrices); ArrayResize(g_sellPrices, size + 1); g_sellPrices[size] = currentPrice; g_basePrice = currentPrice; Print("新基准价格:", g_basePrice); } } else { Print("当前价位", DoubleToString(currentPrice, _Digits), "已经开过空单,跳过"); } } } //+------------------------------------------------------------------+ //| 在数组中搜索指定值(精确匹配) | //+------------------------------------------------------------------+ int ArraySearch(const double &array[], double value) { int size = ArraySize(array); for(int i = 0; i < size; i++) { if(MathAbs(array[i] - value) < _Point*0.5) // 价格相等判断(考虑精度) return i; } return -1; } //+------------------------------------------------------------------+ //| 开多仓 | //+------------------------------------------------------------------+ bool OpenBuyOrder() { MqlTradeRequest request = {}; MqlTradeResult result = {}; uint filling = (uint)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = g_currentLotSize; request.type = ORDER_TYPE_BUY; request.price = ask; request.deviation = 10; request.magic = 123456; // 设置止盈止损 // 使用已计算好的g_sl_points和g_tp_points来设置价格 request.sl = NormalizeDouble(ask - g_sl_points * _Point, _Digits); request.tp = NormalizeDouble(ask + g_tp_points * _Point, _Digits); // 设置填充模式 if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) request.type_filling = ORDER_FILLING_FOK; else if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) request.type_filling = ORDER_FILLING_IOC; else request.type_filling = ORDER_FILLING_RETURN; Print("尝试开多仓:价格=", request.price, " 手数=", request.volume, " 止损=", request.sl, " 止盈=", request.tp, " 填充模式=", request.type_filling); if(!OrderSend(request, result)) { int err=GetLastError(); Print("订单发送失败,错误代码:", (string)err); Print("错误描述:", GetErrorDescription(err)); return false; } g_lastOrderTicket = result.order; Print("订单发送成功!订单号:", result.order); return true; } //+------------------------------------------------------------------+ //| 开空仓 | //+------------------------------------------------------------------+ bool OpenSellOrder() { MqlTradeRequest request = {}; MqlTradeResult result = {}; uint filling = (uint)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = g_currentLotSize; request.type = ORDER_TYPE_SELL; request.price = bid; request.deviation = 10; request.magic = 123456; // 设置止盈止损 request.sl = NormalizeDouble(bid + g_sl_points * _Point, _Digits); request.tp = NormalizeDouble(bid - g_tp_points * _Point, _Digits); // 设置填充模式 if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) request.type_filling = ORDER_FILLING_FOK; else if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) request.type_filling = ORDER_FILLING_IOC; else request.type_filling = ORDER_FILLING_RETURN; Print("尝试开空仓:价格=", request.price, " 手数=", request.volume, " 止损=", request.sl, " 止盈=", request.tp, " 填充模式=", request.type_filling); if(!OrderSend(request, result)) { int err=GetLastError(); Print("订单发送失败,错误代码:", (string)err); Print("错误描述:", GetErrorDescription(err)); return false; } g_lastOrderTicket = result.order; Print("订单发送成功!订单号:", result.order); return true; } //+------------------------------------------------------------------+ //| 获取错误描述 | //+------------------------------------------------------------------+ string GetErrorDescription(int error_code) { string error_string; switch(error_code) { case 4756: error_string = "不支持的填充模式"; break; case 4752: error_string = "交易操作类型无效"; break; case 4751: error_string = "交易参数不正确"; break; case 4758: error_string = "交易品种不可用"; break; case 4755: error_string = "订单类型不正确"; break; default: error_string = "未知错误"; } return error_string; } //+------------------------------------------------------------------+ //| Trade Transaction | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { ENUM_TRADE_TRANSACTION_TYPE type = trans.type; // 只关注成交事件 if(type != TRADE_TRANSACTION_DEAL_ADD) return; ulong dealTicket = trans.deal; if(!HistoryDealSelect(dealTicket)) return; ENUM_DEAL_ENTRY dealEntry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY); if(dealEntry != DEAL_ENTRY_OUT) return; double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); // 简化逻辑:如果盈利>0则视为止盈触发,如果<0则视为止损触发 // 实际中应根据价格判断是何种触发更可靠 if(dealProfit < 0) { HandleStopLoss(); } else if(dealProfit > 0) { HandleTakeProfit(); } } //+------------------------------------------------------------------+ //| 处理止损 | //+------------------------------------------------------------------+ void HandleStopLoss() { Print("=== 开始处理止损 ==="); g_isInProfit = false; Print("=== 止损处理完成 ==="); } //+------------------------------------------------------------------+ //| 处理止盈 | //+------------------------------------------------------------------+ void HandleTakeProfit() { Print("=== 开始处理止盈 ==="); g_isInProfit = true; Print("=== 止盈处理完成 ==="); } //+------------------------------------------------------------------+ //| 更新图表注释 | //+------------------------------------------------------------------+ void UpdateComment(double currentPrice) { string buyPricesStr = ""; for(int i=0; i<ArraySize(g_buyPrices); i++) { if(i > 0) buyPricesStr += ", "; buyPricesStr += DoubleToString(g_buyPrices[i], _Digits); } string sellPricesStr = ""; for(int i=0; i<ArraySize(g_sellPrices); i++) { if(i > 0) sellPricesStr += ", "; sellPricesStr += DoubleToString(g_sellPrices[i], _Digits); } string commentText = "当前价格: " + DoubleToString(currentPrice, 2) + "\n" + "基准价格: " + DoubleToString(g_basePrice, 2) + "\n" + "基础网格间距: " + DoubleToString(GridSize, 2) + "\n" + "买入网格间距: " + DoubleToString(GridSize * BuyGridMultiplier, 2) + "\n" + "卖出网格间距: " + DoubleToString(GridSize * SellGridMultiplier, 2) + "\n" + "买入触发价格: " + DoubleToString(g_basePrice + (GridSize * BuyGridMultiplier), 2) + "\n" + "卖出触发价格: " + DoubleToString(g_basePrice - (GridSize * SellGridMultiplier), 2) + "\n" + "当前手数: " + DoubleToString(g_currentLotSize, 2) + "\n" + "已开多单价格: " + buyPricesStr + "\n" + "已开空单价格: " + sellPricesStr; Comment(commentText); } //+------------------------------------------------------------------+ //| 统计当前品种持仓数 | //+------------------------------------------------------------------+ int CountSymbolPositions() { int count = 0; int total = PositionsTotal(); for(int i=0; i<total; i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetString(POSITION_SYMBOL) == _Symbol) count++; } } return count; }

以上是修改后的完整代码示例。在此版本中:

  • 止盈止损值从美元转换为相应的点差进行设置。
  • 使用OnTimer()每5秒打印信息,减少OnTick()中的冗余输出。
  • 持仓限制和价格记录逻辑更严谨。
  • 根据盈利状态自动倍增手数(4倍)。

请根据实际需求进一步调整和完善策略细节。

分享這個問答