641 lines
No EOL
25 KiB
MQL5
641 lines
No EOL
25 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| RSI_Strategy.mq5 |
|
|
//| Copyright 2025, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
|
#property link "https://www.mql5.com"
|
|
#property version "1.00"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
CTrade trade;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 输入参数 |
|
|
//+------------------------------------------------------------------+
|
|
input group "=====基本参数======"
|
|
input int MagicNumber = 123456;
|
|
input string OrderComment = "RSI_Strategy";
|
|
|
|
input group "=====马丁参数====="
|
|
input double MartinStartLots = 0.1; // 首次进场手数(基准:10000美元=0.1手)
|
|
input double MartinStepUSD = 3; // 马丁加仓间隔(美金,作为ATR不触发时的回退值)
|
|
input double MartinMultiplier = 1.3; // 马丁加仓倍数
|
|
input double TakeProfitUSD = 3; // 盈利出场(美金)
|
|
input int PriceDigits = 2; // 价格小数位(如XAUUSD有的平台是2,有的是3)
|
|
input int MaxAddCount = 15; // 最大加仓次数
|
|
input bool EnableDynamicLots = true; // 是否启用动态手数(按余额比例),关闭则固定用MartinStartLots
|
|
|
|
input group "=====动态加仓参数====="
|
|
input int ATRPeriod = 2; // ATR周期
|
|
input double ATRThresholdUSD = 0.6; // ATR阈值(美金)
|
|
input double ATRDistanceMultiplier = 1;// ATR加仓距离系数
|
|
|
|
input group "=====EMA加仓风控====="
|
|
input bool EnableEMARisk = false; // 开关:当加仓次数超过阈值后启用EMA风控
|
|
input int EMARiskStartAddCount = 10; // 超过该加仓次数后启用风控
|
|
input int EMARiskPeriod = 50; // EMA周期(默认50)
|
|
|
|
input group "=====指标参数====="
|
|
input double RSIUpperLevel = 85; // RSI超买水平
|
|
input double RSILowerLevel = 25; // RSI超卖水平
|
|
input bool EnableRSIFilter = false; // 是否启用RSI过滤
|
|
input double ADXThreshold = 50; // ADX阈值,大于此值时暂停加仓
|
|
input bool EnableADXFilter = true; // 是否启用ADX过滤功能
|
|
|
|
input group "=====交易方向开关====="
|
|
input bool EnableBuy = true; // 是否允许做多
|
|
input bool EnableSell = true; // 是否允许做空
|
|
|
|
input group "=====时间段控制====="
|
|
input bool EnableTimeControl = false; // 是否时间段控制
|
|
input string TradingHours = "1,2,3,4,5,6,7,8,9,10"; // 允许开仓的时间段(小时),用逗号分割
|
|
|
|
// RSI指标句柄
|
|
int rsiHandle = INVALID_HANDLE;
|
|
|
|
// ADX指标句柄
|
|
int adxHandle = INVALID_HANDLE;
|
|
|
|
// ATR指标句柄
|
|
int atrHandle = INVALID_HANDLE;
|
|
|
|
// EMA12指标句柄
|
|
int ema12Handle = INVALID_HANDLE;
|
|
|
|
// EMA风控指标句柄
|
|
int emaRiskHandle = INVALID_HANDLE;
|
|
|
|
// 记录上一次加仓价和当前加仓手数
|
|
static double lastLongAddPrice = 0;
|
|
static double lastLongAddLots = 0;
|
|
|
|
// 记录上一次空单加仓价和手数
|
|
static double lastShortAddPrice = 0;
|
|
static double lastShortAddLots = 0;
|
|
|
|
// 记录加仓次数
|
|
static int buyAddCount = 0;
|
|
static int sellAddCount = 0;
|
|
|
|
// 记录移动追踪止盈的历史最高/最低价
|
|
static double buyTrailingPrice = 0; // 多单移动追踪止盈位
|
|
static double sellTrailingPrice = 0; // 空单移动追踪止盈位
|
|
|
|
// 获取当前RSI值
|
|
double GetRSI() {
|
|
if(rsiHandle == INVALID_HANDLE)
|
|
rsiHandle = iRSI(_Symbol, _Period, 14, PRICE_CLOSE);
|
|
// 获取当前RSI值
|
|
double rsiArray[1];
|
|
if(CopyBuffer(rsiHandle, 0, 0, 1, rsiArray) <= 0) return 0.0;
|
|
return rsiArray[0];
|
|
|
|
// 获取上一根K线的RSI值
|
|
// double rsiArray[2];
|
|
// if(CopyBuffer(rsiHandle, 0, 1, 2, rsiArray) <= 0) return 0.0;
|
|
// return rsiArray[0];
|
|
}
|
|
|
|
// 获取基于ATR的动态加仓距离(单位:美金)。当ATR>阈值时使用 ATR*系数,否则使用固定MartinStepUSD
|
|
double CalculateDynamicAddDistanceUSD() {
|
|
if(atrHandle == INVALID_HANDLE)
|
|
atrHandle = iATR(_Symbol, _Period, ATRPeriod);
|
|
if(atrHandle != INVALID_HANDLE) {
|
|
double atrArray[1];
|
|
if(CopyBuffer(atrHandle, 0, 0, 1, atrArray) == 1) {
|
|
double atrNow = atrArray[0];
|
|
if(atrNow > ATRThresholdUSD) {
|
|
double distance = atrNow * ATRDistanceMultiplier;
|
|
return NormalizeDouble(distance, 2);
|
|
}
|
|
}
|
|
}
|
|
return NormalizeDouble(MartinStepUSD, 2);
|
|
}
|
|
|
|
// 读取上一根K线的开收盘
|
|
bool GetPrevCandle(double &prevOpen, double &prevClose) {
|
|
double o[1], c[1];
|
|
if(CopyOpen(_Symbol, _Period, 1, 1, o) != 1) return false;
|
|
if(CopyClose(_Symbol, _Period, 1, 1, c) != 1) return false;
|
|
prevOpen = o[0];
|
|
prevClose = c[0];
|
|
return true;
|
|
}
|
|
|
|
// 获取指定周期EMA在上一根K线的值
|
|
bool GetPrevEMA(int period, double &emaPrev) {
|
|
if(emaRiskHandle == INVALID_HANDLE)
|
|
emaRiskHandle = iMA(_Symbol, _Period, period, 0, MODE_EMA, PRICE_CLOSE);
|
|
if(emaRiskHandle == INVALID_HANDLE) return false;
|
|
double emaArr[1];
|
|
if(CopyBuffer(emaRiskHandle, 0, 1, 1, emaArr) != 1) return false;
|
|
emaPrev = emaArr[0];
|
|
return true;
|
|
}
|
|
|
|
// EMA风控:
|
|
// - 多单:上一根K线实体上穿EMA(上一根开盘<EMA 且 收盘>EMA)
|
|
// - 空单:上一根K线实体收在EMA下方(上一根收盘<EMA)
|
|
bool PassesEMARisk(int posType) {
|
|
double prevOpen = 0.0, prevClose = 0.0, emaPrev = 0.0;
|
|
if(!GetPrevCandle(prevOpen, prevClose)) return false;
|
|
if(!GetPrevEMA(EMARiskPeriod, emaPrev)) return false;
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
return (prevOpen < emaPrev && prevClose > emaPrev);
|
|
} else {
|
|
return (prevClose < emaPrev);
|
|
}
|
|
}
|
|
|
|
// 获取当前ADX值
|
|
double GetADX() {
|
|
if(adxHandle == INVALID_HANDLE)
|
|
adxHandle = iADX(_Symbol, _Period, 14);
|
|
// 获取当前ADX值
|
|
double adxArray[1];
|
|
if(CopyBuffer(adxHandle, 0, 0, 1, adxArray) <= 0) return 0.0;
|
|
return adxArray[0];
|
|
}
|
|
|
|
// 获取上根K线收盘价
|
|
double GetPreviousClose() {
|
|
double closeArray[2];
|
|
if(CopyClose(_Symbol, _Period, 1, 2, closeArray) <= 0) return 0.0;
|
|
return closeArray[1]; // 上根K线收盘价
|
|
}
|
|
|
|
// 获取当前EMA12值
|
|
double GetEMA12() {
|
|
if(ema12Handle == INVALID_HANDLE)
|
|
ema12Handle = iMA(_Symbol, _Period, 12, 0, MODE_EMA, PRICE_CLOSE);
|
|
double emaArray[2];
|
|
if(CopyBuffer(ema12Handle, 0, 0, 2, emaArray) <= 0) return 0.0;
|
|
return emaArray[1];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 检查当前时间是否在允许开仓的时间段内 |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTradingTime() {
|
|
if(!EnableTimeControl) return true;
|
|
|
|
// 获取当前服务器时间
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(), dt);
|
|
int currentHour = dt.hour;
|
|
|
|
// 解析允许的时间段
|
|
string hours[];
|
|
StringSplit(TradingHours, ',', hours);
|
|
for(int i = 0; i < ArraySize(hours); i++) {
|
|
// 去除空格并转换为整数
|
|
string hourStr = hours[i];
|
|
// 去除前后空格
|
|
while(StringLen(hourStr) > 0 && StringGetCharacter(hourStr, 0) == ' ') {
|
|
hourStr = StringSubstr(hourStr, 1);
|
|
}
|
|
while(StringLen(hourStr) > 0 && StringGetCharacter(hourStr, StringLen(hourStr)-1) == ' ') {
|
|
hourStr = StringSubstr(hourStr, 0, StringLen(hourStr)-1);
|
|
}
|
|
|
|
if(hourStr != "") {
|
|
int hour = (int)StringToInteger(hourStr);
|
|
if(hour == currentHour) {
|
|
return true; // 当前时间在允许的时间段内
|
|
}
|
|
}
|
|
}
|
|
|
|
return false; // 当前时间不在允许的时间段内
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 检查是否有指定方向的持仓 |
|
|
//+------------------------------------------------------------------+
|
|
bool HasPosition(int posType) {
|
|
for(int i=0; i<PositionsTotal(); i++) {
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket)) {
|
|
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
|
|
PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
|
|
PositionGetInteger(POSITION_TYPE) == posType) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 获取当前持仓类型 |
|
|
//+------------------------------------------------------------------+
|
|
int GetCurrentPosType() {
|
|
if(HasPosition(POSITION_TYPE_BUY)) return POSITION_TYPE_BUY;
|
|
if(HasPosition(POSITION_TYPE_SELL)) return POSITION_TYPE_SELL;
|
|
return -1;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 开仓函数 |
|
|
//+------------------------------------------------------------------+
|
|
void OpenPosition(int posType, double lots, double price, string comment) {
|
|
trade.SetExpertMagicNumber(MagicNumber);
|
|
bool result = false;
|
|
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
result = trade.Buy(lots, _Symbol, price, 0, 0, comment);
|
|
} else if(posType == POSITION_TYPE_SELL) {
|
|
result = trade.Sell(lots, _Symbol, price, 0, 0, comment);
|
|
}
|
|
|
|
if(result) {
|
|
string direction = (posType == POSITION_TYPE_BUY) ? "多单" : "空单";
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 执行开仓操作(包含开仓、更新变量、打印日志) |
|
|
//+------------------------------------------------------------------+
|
|
void ExecuteOpenPosition(int posType, double rsi, double rsiThreshold, double curPrice) {
|
|
double dynamicLots = GetDynamicLots(posType);
|
|
OpenPosition(posType, dynamicLots, curPrice, OrderComment);
|
|
|
|
// 更新加仓相关变量
|
|
double lastAddPrice = curPrice;
|
|
double lastAddLots = dynamicLots;
|
|
int currentAddCount = 1; // 首次进场算第一次
|
|
|
|
// 写回全局变量
|
|
UpdateGlobalVariables(posType, lastAddPrice, lastAddLots, currentAddCount);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 平仓函数 |
|
|
//+------------------------------------------------------------------+
|
|
void ClosePosition(int posType) {
|
|
for(int i=PositionsTotal()-1; i>=0; i--) {
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket)) {
|
|
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
|
|
PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
|
|
PositionGetInteger(POSITION_TYPE) == posType) {
|
|
if(trade.PositionClose(ticket)) {
|
|
string direction = (posType == POSITION_TYPE_BUY) ? "多单" : "空单";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 计算指定方向持仓的加权平均价 |
|
|
//+------------------------------------------------------------------+
|
|
double GetAvgPrice(int posType) {
|
|
double totalLots = 0;
|
|
double totalCost = 0;
|
|
for(int i=0; i<PositionsTotal(); i++) {
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket)) {
|
|
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
|
|
PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
|
|
PositionGetInteger(POSITION_TYPE) == posType) {
|
|
double lots = PositionGetDouble(POSITION_VOLUME);
|
|
double price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
totalLots += lots;
|
|
totalCost += lots * price;
|
|
}
|
|
}
|
|
}
|
|
if(totalLots == 0) return 0;
|
|
return totalCost / totalLots;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 计算指定方向持仓的总手数 |
|
|
//+------------------------------------------------------------------+
|
|
double GetTotalLots(int posType) {
|
|
double totalLots = 0;
|
|
for(int i=0; i<PositionsTotal(); i++) {
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket)) {
|
|
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
|
|
PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
|
|
PositionGetInteger(POSITION_TYPE) == posType) {
|
|
totalLots += PositionGetDouble(POSITION_VOLUME);
|
|
}
|
|
}
|
|
}
|
|
return totalLots;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 检查加仓条件 |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckAddPosition(int posType, double curPrice, double lastAddPrice, double stepDistance, double totalLots, int currentAddCount) {
|
|
if(totalLots <= 0) return false;
|
|
|
|
// 检查时间段是否允许开仓
|
|
if(!IsTradingTime()) {
|
|
Print("当前时间不在允许开仓的时间段内,禁止加仓");
|
|
return false;
|
|
}
|
|
|
|
// 检查最大加仓次数
|
|
if(currentAddCount >= MaxAddCount) {
|
|
string direction = (posType == POSITION_TYPE_BUY) ? "多单" : "空单";
|
|
Print(direction, "已达到最大加仓次数(", currentAddCount, " >= ", MaxAddCount, "),停止加仓");
|
|
return false;
|
|
}
|
|
|
|
bool shouldAdd = false;
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
shouldAdd = (curPrice <= lastAddPrice - stepDistance * SymbolInfoDouble(_Symbol, SYMBOL_POINT));
|
|
} else {
|
|
shouldAdd = (curPrice >= lastAddPrice + stepDistance * SymbolInfoDouble(_Symbol, SYMBOL_POINT));
|
|
}
|
|
|
|
if(!shouldAdd) return false;
|
|
|
|
// EMA风控:当开启且加仓次数达到阈值后,要求满足指定的K线与EMA关系
|
|
if(EnableEMARisk && currentAddCount >= EMARiskStartAddCount) {
|
|
if(!PassesEMARisk(posType)) {
|
|
string direction2 = (posType == POSITION_TYPE_BUY) ? "多单" : "空单";
|
|
Print(direction2, " EMA风控未通过(AddCount=", currentAddCount, ", 阈值=", EMARiskStartAddCount,
|
|
", EMA", EMARiskPeriod, "),暂停加仓");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 检查ADX条件(趋势强度)
|
|
if(EnableADXFilter) {
|
|
double adx = GetADX();
|
|
if(adx > ADXThreshold) {
|
|
string direction = (posType == POSITION_TYPE_BUY) ? "多单" : "空单";
|
|
Print(direction, "ADX值过高(", adx, " > ", ADXThreshold, "),暂停加仓");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 写回全局变量 |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateGlobalVariables(int posType, double lastAddPrice, double lastAddLots, int currentAddCount) {
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
lastLongAddPrice = lastAddPrice;
|
|
lastLongAddLots = lastAddLots;
|
|
buyAddCount = currentAddCount;
|
|
} else {
|
|
lastShortAddPrice = lastAddPrice;
|
|
lastShortAddLots = lastAddLots;
|
|
sellAddCount = currentAddCount;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 检查80%移动追踪止盈条件,达到后全部平仓 |
|
|
//+------------------------------------------------------------------+
|
|
void CheckTrailingTakeProfit(int posType, double currentPrice, double avgPrice) {
|
|
// 检查盈亏平衡价是否有效
|
|
if(avgPrice == 0) {
|
|
return;
|
|
}
|
|
|
|
// 计算80%移动追踪止盈价格
|
|
double trailingPrice = 0;
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
// 多单:当前价格 - (当前价格 - 盈亏平衡价) × 80%
|
|
double profitDistance = currentPrice - avgPrice;
|
|
if(profitDistance <= 0) return; // 没有利润,不检查
|
|
trailingPrice = currentPrice - (profitDistance * 0.3);
|
|
|
|
// 更新多单移动追踪止盈位(取最高值)
|
|
if(trailingPrice > buyTrailingPrice || buyTrailingPrice == 0) {
|
|
buyTrailingPrice = trailingPrice;
|
|
Print("多单移动追踪止盈位更新:", buyTrailingPrice, " 当前价:", currentPrice, " 盈亏平衡价:", avgPrice);
|
|
}
|
|
|
|
// 使用历史最高止盈位进行检查
|
|
trailingPrice = buyTrailingPrice;
|
|
} else {
|
|
// 空单:当前价格 + (盈亏平衡价 - 当前价格) × 80%
|
|
double profitDistance = avgPrice - currentPrice;
|
|
if(profitDistance <= 0) return; // 没有利润,不检查
|
|
trailingPrice = currentPrice + (profitDistance * 0.3);
|
|
|
|
// 更新空单移动追踪止盈位(取最低值)
|
|
if(trailingPrice < sellTrailingPrice || sellTrailingPrice == 0) {
|
|
sellTrailingPrice = trailingPrice;
|
|
Print("空单移动追踪止盈位更新:", sellTrailingPrice, " 当前价:", currentPrice, " 盈亏平衡价:", avgPrice);
|
|
}
|
|
|
|
// 使用历史最低止盈位进行检查
|
|
trailingPrice = sellTrailingPrice;
|
|
}
|
|
|
|
// 规范化价格
|
|
trailingPrice = NormalizeDouble(trailingPrice, _Digits);
|
|
|
|
// 检查是否达到移动追踪止盈条件
|
|
bool shouldClose = false;
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
// 多单:当前价格回撤到移动止盈位以下
|
|
shouldClose = (currentPrice <= trailingPrice);
|
|
} else {
|
|
// 空单:当前价格反弹到移动止盈位以上
|
|
shouldClose = (currentPrice >= trailingPrice);
|
|
}
|
|
|
|
if(shouldClose) {
|
|
Print("达到80%移动追踪止盈条件,", (posType == POSITION_TYPE_BUY) ? "多单" : "空单", " 当前价:", currentPrice, " 移动止盈位:", trailingPrice);
|
|
|
|
// 调用现有的平仓函数
|
|
ClosePosition(posType);
|
|
Print("80%移动追踪止盈平仓完成");
|
|
|
|
// 重置移动追踪止盈位
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
buyTrailingPrice = 0;
|
|
} else {
|
|
sellTrailingPrice = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 入场条件检查函数 |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckEntryFirst(int posType, double rsi, double rsiThreshold, double totalLots) {
|
|
// 1. 检查是否已有持仓
|
|
if(totalLots > 0) return false;
|
|
|
|
// 2. 检查时间段是否允许开仓
|
|
if(!IsTradingTime()) {
|
|
Print("当前时间不在允许开仓的时间段内,禁止开仓");
|
|
return false;
|
|
}
|
|
|
|
// 3. 检查RSI过滤条件
|
|
bool shouldEnter = false;
|
|
if(EnableRSIFilter) {
|
|
shouldEnter = (posType == POSITION_TYPE_BUY) ? (rsi < rsiThreshold) : (rsi > rsiThreshold);
|
|
} else {
|
|
shouldEnter = true; // 不用RSI过滤时,只要没持仓就允许开仓
|
|
}
|
|
|
|
if(!shouldEnter) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 计算开仓手数:可选动态手数(按余额比例)或固定手数 |
|
|
//+------------------------------------------------------------------+
|
|
double GetDynamicLots(int posType) {
|
|
// 规范化工具
|
|
double minLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
double stepLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
if(!EnableDynamicLots) {
|
|
// 固定手数模式:直接使用 MartinStartLots,并按交易品种步进规范化
|
|
double fixedLots = MartinStartLots;
|
|
fixedLots = MathMax(minLots, MathMin(maxLots, fixedLots));
|
|
fixedLots = NormalizeDouble(fixedLots / stepLots, 0) * stepLots;
|
|
return fixedLots;
|
|
}
|
|
|
|
// 动态手数模式:当前余额 / 基准余额 * 基准手数
|
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double baseBalance = 10000.0; // 基准余额
|
|
double baseLots = MartinStartLots; // 基准手数
|
|
double dynamicLots = (accountBalance / baseBalance) * baseLots;
|
|
|
|
// 规范化手数
|
|
dynamicLots = MathMax(minLots, MathMin(maxLots, dynamicLots));
|
|
dynamicLots = NormalizeDouble(dynamicLots / stepLots, 0) * stepLots;
|
|
return dynamicLots;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 马丁策略主逻辑 |
|
|
//+------------------------------------------------------------------+
|
|
void MartinLogic(int posType, double rsi, double rsiThreshold) {
|
|
double curPrice = (posType == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
|
|
SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double avgPrice = GetAvgPrice(posType);
|
|
double totalLots = GetTotalLots(posType);
|
|
|
|
double lastAddPrice = (posType == POSITION_TYPE_BUY) ? lastLongAddPrice : lastShortAddPrice;
|
|
double lastAddLots = (posType == POSITION_TYPE_BUY) ? lastLongAddLots : lastShortAddLots;
|
|
int currentAddCount = (posType == POSITION_TYPE_BUY) ? buyAddCount : sellAddCount;
|
|
string direction = (posType == POSITION_TYPE_BUY) ? "多单" : "空单";
|
|
|
|
// 计算实际点数(美金转点数),加仓距离采用动态计算
|
|
int multiplier = (PriceDigits == 2) ? 100 : ((PriceDigits == 3) ? 1000 : 1);
|
|
double stepDistanceUSD = CalculateDynamicAddDistanceUSD();
|
|
double stepDistance = stepDistanceUSD * multiplier;
|
|
double tpDistance = TakeProfitUSD * multiplier;
|
|
|
|
// 1. 首次进场
|
|
bool shouldEnter = CheckEntryFirst(posType, rsi, rsiThreshold, totalLots);
|
|
|
|
if(shouldEnter) {
|
|
ExecuteOpenPosition(posType, rsi, rsiThreshold, curPrice);
|
|
return;
|
|
}
|
|
|
|
// 2. 盈利出场
|
|
bool shouldTakeProfit = false;
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
shouldTakeProfit = (totalLots > 0 && curPrice >= avgPrice + tpDistance * SymbolInfoDouble(_Symbol, SYMBOL_POINT));
|
|
} else {
|
|
shouldTakeProfit = (totalLots > 0 && curPrice <= avgPrice - tpDistance * SymbolInfoDouble(_Symbol, SYMBOL_POINT));
|
|
}
|
|
if(shouldTakeProfit) {
|
|
ClosePosition(posType);
|
|
lastAddPrice = 0;
|
|
lastAddLots = 0;
|
|
currentAddCount = 0;
|
|
|
|
// 为所有同方向持仓设置80%追踪止盈TP
|
|
// CheckTrailingTakeProfit(posType, curPrice, avgPrice);
|
|
|
|
// 写回全局变量
|
|
UpdateGlobalVariables(posType, lastAddPrice, lastAddLots, currentAddCount);
|
|
|
|
// 平仓后立即检查是否可以开新的首仓
|
|
bool shouldEnter = CheckEntryFirst(posType, rsi, rsiThreshold, 0); // 传入0因为刚平仓,没有持仓
|
|
if(shouldEnter) {
|
|
ExecuteOpenPosition(posType, rsi, rsiThreshold, curPrice);
|
|
}
|
|
|
|
return;
|
|
}
|
|
// 4. 马丁加仓
|
|
bool shouldAdd = CheckAddPosition(posType, curPrice, lastAddPrice, stepDistance, totalLots, currentAddCount);
|
|
|
|
if(shouldAdd) {
|
|
double nextLots = NormalizeDouble(lastAddLots * MartinMultiplier, 2);
|
|
OpenPosition(posType, nextLots, curPrice, OrderComment);
|
|
lastAddPrice = curPrice;
|
|
lastAddLots = nextLots;
|
|
currentAddCount++; // 增加加仓次数
|
|
Print("马丁加仓", direction, ",价格:", curPrice, " 手数:", nextLots, " 加仓次数:", currentAddCount, "/", MaxAddCount);
|
|
UpdateGlobalVariables(posType, lastAddPrice, lastAddLots, currentAddCount);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick() {
|
|
double rsi = GetRSI();
|
|
|
|
// 马丁策略逻辑
|
|
if(EnableBuy)
|
|
MartinLogic(POSITION_TYPE_BUY, rsi, RSILowerLevel); // 多单马丁
|
|
if(EnableSell)
|
|
MartinLogic(POSITION_TYPE_SELL, rsi, RSIUpperLevel); // 空单马丁
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit() {
|
|
// 打印允许开仓的时间段
|
|
Print("=== RSI策略EA初始化 ===");
|
|
Print("允许开仓的时间段: ", TradingHours);
|
|
Print("最大加仓次数: ", MaxAddCount);
|
|
Print("ADX过滤功能: ", (EnableADXFilter ? "启用" : "禁用"));
|
|
if(EnableADXFilter) {
|
|
Print("ADX过滤阈值: ", ADXThreshold);
|
|
}
|
|
Print("当前服务器时间: ", TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason) {
|
|
if(rsiHandle != INVALID_HANDLE)
|
|
IndicatorRelease(rsiHandle);
|
|
if(adxHandle != INVALID_HANDLE)
|
|
IndicatorRelease(adxHandle);
|
|
if(ema12Handle != INVALID_HANDLE)
|
|
IndicatorRelease(ema12Handle);
|
|
if(atrHandle != INVALID_HANDLE)
|
|
IndicatorRelease(atrHandle);
|
|
if(emaRiskHandle != INVALID_HANDLE)
|
|
IndicatorRelease(emaRiskHandle);
|
|
} |