EA代码优化建议
Tạo vào: 6 tháng 12, 2024
Trả lời bằng O1 Preview bởi Chat01
Tạo vào: 6 tháng 12, 2024
Trả lời bằng O1 Preview bởi Chat01
请帮我分析下面这段代码,并给出修改意见。//+------------------------------------------------------------------+
//| 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("实际止盈: ", 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);
textif(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);
textPrint("当前价格:", 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;
textfor(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("=== 开始处理止损 ===");
textg_isInProfit = false; Print("=== 止损处理完成 ===");
}
//+------------------------------------------------------------------+
//| 处理止盈 |
//+------------------------------------------------------------------+
void HandleTakeProfit()
{
Print("=== 开始处理止盈 ===");
textg_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;
textswitch(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]);
}
textstring 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);
}
下面是对代码的整体分析及建议修改点:
1. 止损和止盈的计算问题
目前代码中StopLoss
和TakeProfit
的计算方式略显混乱。
EA中注释显示“实际盈利=设定值/10”或“实际亏损=设定值/10”,但目前代码直接使用request.sl = ask - StopLoss;
和request.tp = ask + TakeProfit;
(对于多单)这样的写法,似乎是将StopLoss
、TakeProfit
等值直接当成价格差,而非实际货币盈亏单位。
如果StopLoss
和TakeProfit
的输入值是以货币(如USD)计价,需要将其转换为价格点差。这通常需要使用SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE)
以及合约大小(SYMBOL_TRADE_CONTRACT_SIZE
)等参数来计算。例如:
cppdouble 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_buyPrices
和g_sellPrices
使用整数类型来存储价格(int g_buyPrices[]; int g_sellPrices[];
),但实际价格通常带有小数(例如EURUSD可能有5位小数),使用(int)MathFloor(currentPrice)
可能造成不精确。如果需要记录精确价格:
double[]
,确保记录精确价格点位。3. 关于手数加倍的逻辑
代码中注释写了“盈利时使用4倍手数” (// 盈利时使用4倍手数
),但实际代码中只是 g_currentLotSize = LotSize * 1;
并未实现加倍。需要根据策略修正此处逻辑。例如:
cppif(g_isInProfit) { g_currentLotSize = LotSize * 4; // 真正使用4倍手数 } else { g_currentLotSize = LotSize; }
若当前设计只是测试,请更新注释或逻辑确保一致。
4. 最大订单数判断与品种过滤
if(PositionsTotal() >= MaxOrders)
就不再开新仓。但PositionsTotal()
会返回所有品种的持仓数量(在某些情况下,如果账户多品种交易,这个判断不够精准)。建议使用PositionSelectByTicket
或PositionSelect(_Symbol)
来统计当前品种的持仓数量,确保策略限制只针对本货币对的仓位数量。
例如:
cppint 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_FOK
或ORDER_FILLING_IOC
。已做判断算是不错,可以在遇到不支持的模式时给出更详细的用户提示,或在输入参数中提供可选模式设置。
8. 代码结构化与可维护性
当前代码逻辑较为集中在OnTick()
中,建议将订单开仓逻辑、判断逻辑、打印逻辑分函数处理。例如:
CheckEntryConditions()
:检查是否需要下新单OpenPositions()
:开仓函数(已经有OpenBuyOrder/OpenSellOrder,但可以再结构化)ManagePositions()
:对已有仓位进行盈利/止损检查PrintStatus()
:专门处理打印信息这样可维护性更高。
总结修改建议:
double
数组存储价格,确保精确性。g_isInProfit
状态下的手数计算逻辑,与注释保持一致。OnTimer()
或减少不必要的日志输出。通过上述修改,代码将更符合实际交易要求,更易维护并更易调试。
好的,请按照你的修改意见,给我一份修改后的完整代码。
下面是修改后的完整代码示例。修改内容包括:
止盈止损计算方式调整:
使用tick_value
和contract_size
将输入的美元止盈止损值转换为价格点差,并在开仓时正确设置止盈止损价格。
网格价位数组类型修改:
将g_buyPrices
和g_sellPrices
从int
改为double
数组,以便记录准确的价格。
手数倍增逻辑修正:
若g_isInProfit == true
,则使用4倍基础手数。
持仓限制以本品种为准:
添加CountSymbolPositions()
函数,仅对当前交易品种的持仓数量进行统计。
日志与性能优化:
使用OnTimer()
事件每5秒输出一次状态信息,而非在OnTick()
中反复检查时间。
在OnInit()
中调用EventSetTimer(5)
实现定时器。
止盈止损触发逻辑说明:
依旧通过OnTradeTransaction()
中的dealProfit
判断止盈止损,但在注释中提示可更精确的方法。
代码结构化与可维护性:
将打印与状态更新挪至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()
中的冗余输出。请根据实际需求进一步调整和完善策略细节。