//+------------------------------------------------------------------+
//| DualMA_MACD_Filter.mq5 |
//| Generated by OpenAI |
//| |
//+------------------------------------------------------------------+
#property copyright "OpenAI"
#property link ""
#property version "1.95"
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
//--- 输入参数
input group "=== 均线设置 ==="
input int InpFastMA = 1; // 快线周期(K线根数)
input ENUM_TIMEFRAMES InpFastMATimeframe = PERIOD_CURRENT; // 快线图表周期
input int InpSlowMA = 72; // 慢线周期(K线根数)
input ENUM_TIMEFRAMES InpSlowMATimeframe = PERIOD_CURRENT; // 慢线图表周期
input int InpFilterMA = 144; // 过滤均线周期(K线根数)
input ENUM_TIMEFRAMES InpFilterMATimeframe = PERIOD_CURRENT; // 过滤线图表周期
input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // 均线类型
input bool InpOpenOnCross = true; // 仅在均线交叉时开仓(true=交叉开仓,false=持续满足即开仓)
input group "=== MACD 设置 ==="
input int InpMACDFast = 6; // MACD 快线
input int InpMACDSlow = 13; // MACD 慢线
input int InpMACDSignal = 5; // MACD 信号线
input bool InpUseMACDValidBars = false; // 启用能量柱有效根数限制(false=仅要求能量柱方向)
input int InpMACDValidBars = 2; // MACD能量柱有效根数(穿越零轴后允许开仓的K线数)
input group "=== 资金管理(复利手数) ==="
input double InpMoneyPerLot = 1000.0; // 每多少资金对应基础手数
input double InpBaseLot = 0.01; // 基础手数(每 InpMoneyPerLot 资金对应的手数)
input double InpLotIncrement = 0.01; // 亏损递增步长(固定增加)
input double InpMaxLots = 1.0; // 最大手数上限(达到后不再增加,但继续交易)
input double InpDailyProfitLimit= 500.0; // 每日盈利限制(美元,达到后暂停开仓)
input group "=== 出场设置 ==="
input bool InpUseFixedSLTP = true; // 启用固定止盈止损
input int InpTakeProfit = 0; // 止盈点数(0=禁用)
input int InpStopLoss = 0; // 止损点数(0=禁用)
input bool InpUseReverse = true; // 启用均线反转反手
input bool InpUseFilterClose = true; // 启用过滤均线平仓(收盘价穿越过滤线时平仓)
input group "=== 移动止损 ==="
input bool InpUseTrailingStop = false; // 启用移动止损
input int InpTrailingStart = 1000; // 触发移动止损的盈利点数
input int InpTrailingStep = 1000; // 移动止损距离点数
input group "=== 交易时间限制(服务器时间) ==="
input string InpStartTime = "00:00"; // 允许开首单的开始时间 (HH:MM)
input string InpStopTime = "23:59"; // 允许开首单的暂停时间 (HH:MM)
input group "=== 其他 ==="
input long InpMagicNumber = 16887; // 魔术号
input bool InpUseFilter = true; // 启用过滤均线(开仓方向过滤)
//--- 全局变量
CTrade trade;
CPositionInfo posInfo;
double g_currentLot = 0.0; // 当前应使用的开仓手数(动态计算)
double g_dailyProfit = 0.0; // 当日已实现盈利(美元)
datetime g_lastBarTime = 0; // 上一根K线时间
int g_lastDay = -1; // 上次记录的日号(用于重置每日盈利)
//--- 移动止损专用
double g_trailingStopPrice = 0.0; // 当前移动止损价位
double g_extremePrice = 0.0; // 持仓期间最高(多单)或最低(空单)价
bool g_trailingTriggered = false; // 是否已触发移动止损
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 检查 MACD 有效根数
if(InpUseMACDValidBars && InpMACDValidBars < 1)
{
Print("错误:MACD有效根数必须>=1");
return(INIT_PARAMETERS_INCORRECT);
}
// 检查时间格式
if(!IsValidTimeFormat(InpStartTime) || !IsValidTimeFormat(InpStopTime))
{
Print("错误:时间格式必须为 HH:MM");
return(INIT_PARAMETERS_INCORRECT);
}
// 检查资金管理参数
if(InpMoneyPerLot <= 0 || InpBaseLot <= 0)
{
Print("错误:每手对应资金和基础手数必须大于0");
return(INIT_PARAMETERS_INCORRECT);
}
// 初始化当前手数为动态计算的首单手数
g_currentLot = CalculateDynamicLot();
g_dailyProfit = 0.0;
g_lastBarTime = 0;
g_lastDay = -1;
// 重置移动止损变量
g_trailingStopPrice = 0.0;
g_extremePrice = 0.0;
g_trailingTriggered = false;
trade.SetExpertMagicNumber(InpMagicNumber);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 检查是否新的一天(服务器时间),重置当日盈利
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.day != g_lastDay)
{
if(g_lastDay != -1)
g_dailyProfit = 0.0;
g_lastDay = dt.day;
}
//--- 1. 实时检查固定止盈止损(每个tick)
if(InpUseFixedSLTP)
CheckFixedSLTP();
//--- 2. 实时检查移动止损(每个tick)
CheckTrailingStop();
//--- 3. 检测新K线
datetime currentBarTime = iTime(_Symbol, _Period, 0);
bool isNewBar = (currentBarTime != g_lastBarTime);
if(!isNewBar)
return;
g_lastBarTime = currentBarTime;
//--- 4. 获取指标数据(基于各自周期的前一根K线,即shift=1)
double fastMA[1], slowMA[1], filterMA[1];
double macdHist[];
int macdBarsNeeded = (InpUseMACDValidBars) ? InpMACDValidBars + 1 : 1;
if(!GetIndicatorValues(fastMA, slowMA, filterMA, macdHist, macdBarsNeeded))
return;
double closePrice = iClose(_Symbol, _Period, 1); // 当前图表周期的前一根K线收盘价
//--- 5. 检查现有持仓
bool hasPosition = PositionSelect(_Symbol);
if(hasPosition)
{
long posType = PositionGetInteger(POSITION_TYPE);
// 5.1 过滤均线平仓(使用过滤线的周期)
if(InpUseFilterClose)
{
bool closeSignal = false;
if(posType == POSITION_TYPE_BUY && closePrice < filterMA[0])
closeSignal = true;
else if(posType == POSITION_TYPE_SELL && closePrice > filterMA[0])
closeSignal = true;
if(closeSignal)
{
CloseCurrentPosition();
return;
}
}
// 5.2 均线反转平仓反手(使用快慢线的周期)
if(InpUseReverse)
{
double prevFast = GetPreviousMA(InpFastMA, InpFastMATimeframe);
double prevSlow = GetPreviousMA(InpSlowMA, InpSlowMATimeframe);
bool crossDown = (fastMA[0] < slowMA[0] && prevFast > prevSlow); // 死叉
bool crossUp = (fastMA[0] > slowMA[0] && prevFast < prevSlow); // 金叉
if(posType == POSITION_TYPE_BUY && crossDown)
{
CloseCurrentPosition();
if(!InpUseFilter || closePrice < filterMA[0])
OpenSell();
return;
}
else if(posType == POSITION_TYPE_SELL && crossUp)
{
CloseCurrentPosition();
if(!InpUseFilter || closePrice > filterMA[0])
OpenBuy();
return;
}
}
return;
}
else
{
//--- 6. 无持仓时,检测开仓信号
if(g_dailyProfit >= InpDailyProfitLimit)
return;
if(!IsTradingTimeAllowed())
return;
bool macdBullish = false;
bool macdBearish = false;
if(InpUseMACDValidBars)
{
if(macdHist[0] > 0)
{
for(int i=1; i<=InpMACDValidBars && i<ArraySize(macdHist); i++)
{
if(macdHist[i] <= 0)
{
macdBullish = true;
break;
}
}
}
else if(macdHist[0] < 0)
{
for(int i=1; i<=InpMACDValidBars && i<ArraySize(macdHist); i++)
{
if(macdHist[i] >= 0)
{
macdBearish = true;
break;
}
}
}
}
else
{
macdBullish = (macdHist[0] > 0);
macdBearish = (macdHist[0] < 0);
}
//--- 均线方向判断(使用快慢线各自的周期)
bool maBullish = (fastMA[0] > slowMA[0]);
bool maBearish = (fastMA[0] < slowMA[0]);
//--- 如果启用交叉开仓,则要求发生金叉/死叉(使用前一根值)
if(InpOpenOnCross)
{
double prevFast = GetPreviousMA(InpFastMA, InpFastMATimeframe);
double prevSlow = GetPreviousMA(InpSlowMA, InpSlowMATimeframe);
bool crossUp = (maBullish && prevFast <= prevSlow); // 金叉
bool crossDown = (maBearish && prevFast >= prevSlow); // 死叉
maBullish = crossUp;
maBearish = crossDown;
}
bool buySignal = maBullish && macdBullish;
bool sellSignal = maBearish && macdBearish;
if(InpUseFilter)
{
buySignal = buySignal && (closePrice > filterMA[0]);
sellSignal = sellSignal && (closePrice < filterMA[0]);
}
if(buySignal)
OpenBuy();
else if(sellSignal)
OpenSell();
}
}
//+------------------------------------------------------------------+
//| 计算动态首单手数 |
//+------------------------------------------------------------------+
double CalculateDynamicLot()
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double lot = balance / InpMoneyPerLot * InpBaseLot;
return NormalizeLot(lot);
}
//+------------------------------------------------------------------+
//| 检查交易时间是否允许开首单 |
//+------------------------------------------------------------------+
bool IsTradingTimeAllowed()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int currentMinutes = dt.hour * 60 + dt.min;
int startMinutes = ParseTimeToMinutes(InpStartTime);
int stopMinutes = ParseTimeToMinutes(InpStopTime);
if(startMinutes < stopMinutes)
return (currentMinutes >= startMinutes && currentMinutes < stopMinutes);
else if(startMinutes > stopMinutes)
return (currentMinutes >= startMinutes || currentMinutes < stopMinutes);
else
return true; // 开始等于暂停,全天允许
}
//+------------------------------------------------------------------+
//| 解析 "HH:MM" 字符串为分钟数 |
//+------------------------------------------------------------------+
int ParseTimeToMinutes(string timeStr)
{
string parts[];
if(StringSplit(timeStr, ':', parts) != 2)
return 0;
int hours = (int)StringToInteger(parts[0]);
int minutes = (int)StringToInteger(parts[1]);
if(hours < 0 || hours > 23 || minutes < 0 || minutes > 59)
return 0;
return hours * 60 + minutes;
}
//+------------------------------------------------------------------+
//| 检查时间字符串格式是否正确 |
//+------------------------------------------------------------------+
bool IsValidTimeFormat(string timeStr)
{
string parts[];
if(StringSplit(timeStr, ':', parts) != 2)
return false;
int hours = (int)StringToInteger(parts[0]);
int minutes = (int)StringToInteger(parts[1]);
if(hours < 0 || hours > 23 || minutes < 0 || minutes > 59)
return false;
return true;
}
//+------------------------------------------------------------------+
//| 获取指标数据(前一根K线,各自周期) |
//+------------------------------------------------------------------+
bool GetIndicatorValues(double &fastMA[], double &slowMA[], double &filterMA[], double &macdHist[], int macdBars)
{
// 使用各自指定的时间框架创建MA句柄
int fastHandle = iMA(_Symbol, InpFastMATimeframe, InpFastMA, 0, InpMAMethod, PRICE_CLOSE);
int slowHandle = iMA(_Symbol, InpSlowMATimeframe, InpSlowMA, 0, InpMAMethod, PRICE_CLOSE);
int filterHandle = iMA(_Symbol, InpFilterMATimeframe, InpFilterMA, 0, InpMAMethod, PRICE_CLOSE);
int macdHandle = iOsMA(_Symbol, _Period, InpMACDFast, InpMACDSlow, InpMACDSignal, PRICE_CLOSE); // MACD仍用当前图表周期
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE ||
filterHandle == INVALID_HANDLE || macdHandle == INVALID_HANDLE)
{
Print("指标创建失败");
return false;
}
// 从各周期均线复制数据(shift=1,即前一根K线)
if(CopyBuffer(fastHandle, 0, 1, 1, fastMA) != 1 ||
CopyBuffer(slowHandle, 0, 1, 1, slowMA) != 1 ||
CopyBuffer(filterHandle, 0, 1, 1, filterMA) != 1)
{
Print("均线数据复制失败");
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
IndicatorRelease(filterHandle);
IndicatorRelease(macdHandle);
return false;
}
ArrayResize(macdHist, macdBars);
if(CopyBuffer(macdHandle, 0, 1, macdBars, macdHist) != macdBars)
{
Print("MACD数据复制失败,需要", macdBars, "根,实际复制不足");
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
IndicatorRelease(filterHandle);
IndicatorRelease(macdHandle);
return false;
}
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
IndicatorRelease(filterHandle);
IndicatorRelease(macdHandle);
return true;
}
//+------------------------------------------------------------------+
//| 获取指定均线的上一根K线值(用于交叉判断,指定周期) |
//+------------------------------------------------------------------+
double GetPreviousMA(int period, ENUM_TIMEFRAMES tf)
{
double buf[];
int handle = iMA(_Symbol, tf, period, 0, InpMAMethod, PRICE_CLOSE);
if(handle == INVALID_HANDLE) return 0;
// 复制前两根K线中的第二根(即shift=2,索引2对应更早一根,我们取上一根即shift=1?注意:当前值使用shift=1,前一根值应使用shift=2)
// 但GetIndicatorValues中当前值用了shift=1,因此前一根值应使用shift=2
if(CopyBuffer(handle, 0, 2, 1, buf) != 1)
{
IndicatorRelease(handle);
return 0;
}
IndicatorRelease(handle);
return buf[0];
}
//+------------------------------------------------------------------+
//| 开多单 |
//+------------------------------------------------------------------+
void OpenBuy()
{
if(g_dailyProfit >= InpDailyProfitLimit)
return;
if(!IsTradingTimeAllowed())
return;
// 重置移动止损变量(新仓)
g_trailingStopPrice = 0.0;
g_extremePrice = 0.0;
g_trailingTriggered = false;
double lots = NormalizeLot(g_currentLot);
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(trade.Buy(lots, _Symbol, price, 0, 0, "DualMA_MACD EA Buy"))
Print("开多成功,手数: ", lots);
else
Print("开多失败: ", trade.ResultRetcode());
}
//+------------------------------------------------------------------+
//| 开空单 |
//+------------------------------------------------------------------+
void OpenSell()
{
if(g_dailyProfit >= InpDailyProfitLimit)
return;
if(!IsTradingTimeAllowed())
return;
// 重置移动止损变量(新仓)
g_trailingStopPrice = 0.0;
g_extremePrice = 0.0;
g_trailingTriggered = false;
double lots = NormalizeLot(g_currentLot);
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(trade.Sell(lots, _Symbol, price, 0, 0, "DualMA_MACD EA Sell"))
Print("开空成功,手数: ", lots);
else
Print("开空失败: ", trade.ResultRetcode());
}
//+------------------------------------------------------------------+
//| 平掉当前持仓并更新下一手数和每日盈利 |
//+------------------------------------------------------------------+
void CloseCurrentPosition()
{
if(!PositionSelect(_Symbol))
return;
long posTicket = PositionGetInteger(POSITION_TICKET);
double posProfit = PositionGetDouble(POSITION_PROFIT);
double posLots = PositionGetDouble(POSITION_VOLUME);
if(trade.PositionClose(posTicket))
{
// 更新下一笔开仓手数
if(posProfit > 0)
{
// 盈利:重置为动态计算的首单手数
g_currentLot = CalculateDynamicLot();
}
else
{
// 亏损:在上一手数基础上固定增加步长
g_currentLot = MathMin(g_currentLot + InpLotIncrement, InpMaxLots);
g_currentLot = NormalizeLot(g_currentLot);
}
// 确保不小于最小手数
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
if(g_currentLot < minLot) g_currentLot = minLot;
// 累计当日盈利(已实现)
g_dailyProfit += posProfit;
}
else
Print("平仓失败,票据: ", posTicket, " 错误: ", trade.ResultRetcode());
// 平仓后重置移动止损变量
g_trailingStopPrice = 0.0;
g_extremePrice = 0.0;
g_trailingTriggered = false;
}
//+------------------------------------------------------------------+
//| 检查固定止盈止损(实时) |
//+------------------------------------------------------------------+
void CheckFixedSLTP()
{
if(!PositionSelect(_Symbol))
return;
long posType = PositionGetInteger(POSITION_TYPE);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentPrice;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double profitPoints = 0;
if(posType == POSITION_TYPE_BUY)
{
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
profitPoints = (currentPrice - openPrice) / point;
}
else if(posType == POSITION_TYPE_SELL)
{
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
profitPoints = (openPrice - currentPrice) / point;
}
else
return;
// 移动止损触发后,仅禁用固定止损,固定止盈仍然有效
bool checkStopLoss = true;
if(InpUseTrailingStop && g_trailingTriggered)
checkStopLoss = false;
// 固定止盈(始终检查)
if(InpTakeProfit > 0 && profitPoints >= InpTakeProfit)
{
CloseCurrentPosition();
return;
}
// 固定止损(仅在未触发移动止损时检查)
if(checkStopLoss && InpStopLoss > 0 && profitPoints <= -InpStopLoss)
{
CloseCurrentPosition();
return;
}
}
//+------------------------------------------------------------------+
//| 检查移动止损(实时) |
//+------------------------------------------------------------------+
void CheckTrailingStop()
{
if(!InpUseTrailingStop)
return;
if(!PositionSelect(_Symbol))
return;
long posType = PositionGetInteger(POSITION_TYPE);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentPrice;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double profitPoints;
if(posType == POSITION_TYPE_BUY)
{
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
profitPoints = (currentPrice - openPrice) / point;
// 达到触发条件后开始跟踪
if(profitPoints >= InpTrailingStart)
{
// 更新最高价
if(!g_trailingTriggered || currentPrice > g_extremePrice)
{
g_extremePrice = currentPrice;
g_trailingTriggered = true;
g_trailingStopPrice = g_extremePrice - InpTrailingStep * point;
}
// 检查是否触及止损
if(currentPrice <= g_trailingStopPrice)
{
CloseCurrentPosition();
return;
}
}
}
else if(posType == POSITION_TYPE_SELL)
{
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
profitPoints = (openPrice - currentPrice) / point;
if(profitPoints >= InpTrailingStart)
{
// 更新最低价
if(!g_trailingTriggered || currentPrice < g_extremePrice)
{
g_extremePrice = currentPrice;
g_trailingTriggered = true;
g_trailingStopPrice = g_extremePrice + InpTrailingStep * point;
}
// 检查是否触及止损
if(currentPrice >= g_trailingStopPrice)
{
CloseCurrentPosition();
return;
}
}
}
}
//+------------------------------------------------------------------+
//| 手数规范化 |
//+------------------------------------------------------------------+
double NormalizeLot(double lot)
{
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lot = MathMax(minLot, MathMin(maxLot, lot));
lot = MathFloor(lot / step) * step;
return lot;
}
//+------------------------------------------------------------------+
|