416 lines
16 KiB
MQL5
416 lines
16 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Pinbar_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 = 123457;
|
|
input string OrderComment = "Pinbar_Strategy";
|
|
|
|
input group "=====交易参数====="
|
|
input double StartLots = 0.1; // 开仓手数
|
|
input bool EnableDynamicLots = true; // 是否启用动态手数(按余额比例)
|
|
input double BaseBalance = 10000.0; // 基准余额(用于动态手数计算)
|
|
|
|
input group "=====Pinbar识别参数====="
|
|
input double PinbarBodyRatio = 0.3; // Pinbar实体比例(实体/总长度)
|
|
input double PinbarShadowRatio = 0.6; // Pinbar影线比例(影线/总长度)
|
|
input int MinPinbarLength = 10; // 最小Pinbar长度(点数)
|
|
|
|
input group "=====交易方向开关====="
|
|
input bool EnableBuy = true; // 是否允许做多
|
|
input bool EnableSell = true; // 是否允许做空
|
|
|
|
input group "=====ATR控制====="
|
|
input bool EnableATRFilter = true; // 是否启用ATR过滤
|
|
input int ATRPeriod = 14; // ATR周期
|
|
input double ATRThreshold = 0.0003; // ATR阈值,大于此值才允许开仓
|
|
|
|
input group "=====交易量过滤====="
|
|
input bool EnableVolumeFilter = true; // 是否启用交易量过滤
|
|
input double VolumeIncreaseRatio = 0.2; // 交易量增加比例(20% = 0.2)
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 全局变量 |
|
|
//+------------------------------------------------------------------+
|
|
datetime lastPinbarTime = 0; // 上次识别到Pinbar的时间
|
|
bool isPinbarProcessed = false; // 当前Pinbar是否已处理
|
|
|
|
// ATR指标句柄
|
|
int atrHandle = INVALID_HANDLE;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Pinbar结构体 |
|
|
//+------------------------------------------------------------------+
|
|
struct PinbarInfo {
|
|
bool isValid; // 是否为有效Pinbar
|
|
bool isBullish; // 是否为看涨Pinbar
|
|
double open; // 开盘价
|
|
double high; // 最高价
|
|
double low; // 最低价
|
|
double close; // 收盘价
|
|
double bodySize; // 实体大小
|
|
double upperShadow; // 上影线
|
|
double lowerShadow; // 下影线
|
|
double totalLength; // 总长度
|
|
datetime time; // K线时间
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 获取K线数据 |
|
|
//+------------------------------------------------------------------+
|
|
bool GetCandleData(int shift, double &open, double &high, double &low, double &close, datetime &time) {
|
|
double o[1], h[1], l[1], c[1];
|
|
datetime t[1];
|
|
|
|
if(CopyOpen(_Symbol, _Period, shift, 1, o) != 1) return false;
|
|
if(CopyHigh(_Symbol, _Period, shift, 1, h) != 1) return false;
|
|
if(CopyLow(_Symbol, _Period, shift, 1, l) != 1) return false;
|
|
if(CopyClose(_Symbol, _Period, shift, 1, c) != 1) return false;
|
|
if(CopyTime(_Symbol, _Period, shift, 1, t) != 1) return false;
|
|
|
|
open = o[0];
|
|
high = h[0];
|
|
low = l[0];
|
|
close = c[0];
|
|
time = t[0];
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 识别Pinbar形态 |
|
|
//+------------------------------------------------------------------+
|
|
PinbarInfo IdentifyPinbar(int shift = 1) {
|
|
PinbarInfo pinbar;
|
|
pinbar.isValid = false;
|
|
|
|
double open, high, low, close;
|
|
datetime time;
|
|
|
|
if(!GetCandleData(shift, open, high, low, close, time)) {
|
|
return pinbar;
|
|
}
|
|
|
|
// 计算K线各部分
|
|
pinbar.open = open;
|
|
pinbar.high = high;
|
|
pinbar.low = low;
|
|
pinbar.close = close;
|
|
pinbar.time = time;
|
|
|
|
pinbar.bodySize = MathAbs(close - open);
|
|
pinbar.totalLength = high - low;
|
|
|
|
if(open > close) {
|
|
// 阴线
|
|
pinbar.upperShadow = high - open;
|
|
pinbar.lowerShadow = close - low;
|
|
} else {
|
|
// 阳线
|
|
pinbar.upperShadow = high - close;
|
|
pinbar.lowerShadow = open - low;
|
|
}
|
|
|
|
// 检查是否为有效Pinbar
|
|
if(pinbar.totalLength <= 0) return pinbar;
|
|
|
|
// 检查最小长度
|
|
double minLength = MinPinbarLength * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
if(pinbar.totalLength < minLength) return pinbar;
|
|
|
|
// 检查实体比例
|
|
double bodyRatio = pinbar.bodySize / pinbar.totalLength;
|
|
if(bodyRatio > PinbarBodyRatio) return pinbar;
|
|
|
|
// 检查影线比例
|
|
double shadowRatio = MathMax(pinbar.upperShadow, pinbar.lowerShadow) / pinbar.totalLength;
|
|
if(shadowRatio < PinbarShadowRatio) return pinbar;
|
|
|
|
// 判断Pinbar方向
|
|
if(pinbar.lowerShadow > pinbar.upperShadow) {
|
|
// 下影线长,看涨Pinbar
|
|
pinbar.isBullish = true;
|
|
} else if(pinbar.upperShadow > pinbar.lowerShadow) {
|
|
// 上影线长,看空Pinbar
|
|
pinbar.isBullish = false;
|
|
} else {
|
|
// 影线相等,不是有效Pinbar
|
|
return pinbar;
|
|
}
|
|
|
|
pinbar.isValid = true;
|
|
return pinbar;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 检查是否有指定方向的持仓 |
|
|
//+------------------------------------------------------------------+
|
|
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;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 获取当前ATR值 |
|
|
//+------------------------------------------------------------------+
|
|
double GetATR() {
|
|
if(atrHandle == INVALID_HANDLE)
|
|
atrHandle = iATR(_Symbol, _Period, ATRPeriod);
|
|
|
|
double atrArray[1];
|
|
if(CopyBuffer(atrHandle, 0, 0, 1, atrArray) <= 0) return 0.0;
|
|
return atrArray[0];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 检查ATR过滤条件 |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckATRFilter() {
|
|
if(!EnableATRFilter) return true;
|
|
|
|
double currentATR = GetATR();
|
|
|
|
if(currentATR < ATRThreshold) {
|
|
Print("ATR过滤:当前ATR(", currentATR, ") < 阈值(", ATRThreshold, "),禁止开仓");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 检查交易量过滤条件 |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckVolumeFilter() {
|
|
if(!EnableVolumeFilter) return true;
|
|
|
|
// 获取当前K线和上一根K线的交易量
|
|
long currentVolume[1], prevVolume[1];
|
|
|
|
if(CopyTickVolume(_Symbol, _Period, 1, 1, currentVolume) != 1) {
|
|
Print("无法获取当前K线交易量");
|
|
return false;
|
|
}
|
|
|
|
if(CopyTickVolume(_Symbol, _Period, 2, 1, prevVolume) != 1) {
|
|
Print("无法获取上一根K线交易量");
|
|
return false;
|
|
}
|
|
|
|
// 计算交易量增加比例
|
|
double volumeRatio = (double)(currentVolume[0] - prevVolume[0]) / prevVolume[0];
|
|
|
|
if(volumeRatio < VolumeIncreaseRatio) {
|
|
Print("交易量过滤:当前交易量增加比例(", volumeRatio * 100, "%) < 阈值(", VolumeIncreaseRatio * 100, "%),禁止开仓");
|
|
return false;
|
|
}
|
|
|
|
Print("交易量过滤通过:当前交易量增加比例(", volumeRatio * 100, "%) >= 阈值(", VolumeIncreaseRatio * 100, "%)");
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 计算开仓手数:可选动态手数(按余额比例)或固定手数 |
|
|
//+------------------------------------------------------------------+
|
|
double GetDynamicLots() {
|
|
// 规范化工具
|
|
double minLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
double stepLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
if(!EnableDynamicLots) {
|
|
// 固定手数模式:直接使用 StartLots,并按交易品种步进规范化
|
|
double fixedLots = StartLots;
|
|
fixedLots = MathMax(minLots, MathMin(maxLots, fixedLots));
|
|
fixedLots = NormalizeDouble(fixedLots / stepLots, 0) * stepLots;
|
|
return fixedLots;
|
|
}
|
|
|
|
// 动态手数模式:当前余额 / 基准余额 * 基准手数
|
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double baseLots = StartLots; // 基准手数
|
|
double dynamicLots = (accountBalance / BaseBalance) * baseLots;
|
|
|
|
// 规范化手数
|
|
dynamicLots = MathMax(minLots, MathMin(maxLots, dynamicLots));
|
|
dynamicLots = NormalizeDouble(dynamicLots / stepLots, 0) * stepLots;
|
|
return dynamicLots;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 开仓函数 |
|
|
//+------------------------------------------------------------------+
|
|
void OpenPosition(int posType, double lots, double price, double sl, double tp, string comment) {
|
|
trade.SetExpertMagicNumber(MagicNumber);
|
|
bool result = false;
|
|
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
result = trade.Buy(lots, _Symbol, price, sl, tp, comment);
|
|
} else if(posType == POSITION_TYPE_SELL) {
|
|
result = trade.Sell(lots, _Symbol, price, sl, tp, comment);
|
|
}
|
|
|
|
if(result) {
|
|
string direction = (posType == POSITION_TYPE_BUY) ? "多单" : "空单";
|
|
Print("开仓成功:价格:", price, " 手数:", lots, " 止损:", sl, " 止盈:", tp);
|
|
} else {
|
|
Print("开仓失败: 错误代码:", trade.ResultRetcode(), " 错误描述:", trade.ResultRetcodeDescription());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 计算止盈止损价格 |
|
|
//+------------------------------------------------------------------+
|
|
void CalculateStopLossAndTakeProfit(PinbarInfo &pinbar, double &sl, double &tp) {
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
if(pinbar.isBullish) {
|
|
// 看涨Pinbar:止损在Pinbar最低点,止盈为开仓点向上Pinbar整体长度的距离
|
|
sl = pinbar.low;
|
|
tp = pinbar.close + pinbar.totalLength;
|
|
} else {
|
|
// 看空Pinbar:止损在Pinbar最高点,止盈为开仓点向下Pinbar整体长度的距离
|
|
sl = pinbar.high;
|
|
tp = pinbar.close - pinbar.totalLength;
|
|
}
|
|
|
|
// 规范化价格
|
|
sl = NormalizeDouble(sl, _Digits);
|
|
tp = NormalizeDouble(tp, _Digits);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 处理Pinbar信号 |
|
|
//+------------------------------------------------------------------+
|
|
void ProcessPinbarSignal(PinbarInfo &pinbar) {
|
|
// 检查是否已处理过这个Pinbar
|
|
if(pinbar.time == lastPinbarTime && isPinbarProcessed) {
|
|
return;
|
|
}
|
|
|
|
// 检查ATR过滤条件
|
|
if(!CheckATRFilter()) {
|
|
return;
|
|
}
|
|
|
|
// 检查交易量过滤条件
|
|
if(!CheckVolumeFilter()) {
|
|
return;
|
|
}
|
|
|
|
// 移除持仓数量限制,允许同时持有多空仓位
|
|
|
|
// 确定交易方向
|
|
int posType = -1;
|
|
if(pinbar.isBullish && EnableBuy) {
|
|
posType = POSITION_TYPE_BUY;
|
|
} else if(!pinbar.isBullish && EnableSell) {
|
|
posType = POSITION_TYPE_SELL;
|
|
}
|
|
|
|
if(posType == -1) {
|
|
Print("交易方向被禁用,跳过Pinbar信号");
|
|
return;
|
|
}
|
|
|
|
// 计算开仓手数
|
|
double lots = GetDynamicLots();
|
|
|
|
// 计算止盈止损
|
|
double sl, tp;
|
|
CalculateStopLossAndTakeProfit(pinbar, sl, tp);
|
|
|
|
// 获取开仓价格
|
|
double price = (posType == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
|
|
SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
// 开仓
|
|
string direction = (posType == POSITION_TYPE_BUY) ? "看涨" : "看空";
|
|
string comment = OrderComment + "_" + direction + "Pinbar";
|
|
|
|
OpenPosition(posType, lots, price, sl, tp, comment);
|
|
|
|
// 更新处理状态
|
|
lastPinbarTime = pinbar.time;
|
|
isPinbarProcessed = true;
|
|
|
|
Print("处理", direction, "Pinbar信号完成 - 时间:", TimeToString(pinbar.time),
|
|
" 开仓价:", price, " 止损:", sl, " 止盈:", tp);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick() {
|
|
// 识别上根K线的Pinbar形态
|
|
PinbarInfo pinbar = IdentifyPinbar(1);
|
|
|
|
if(pinbar.isValid) {
|
|
ProcessPinbarSignal(pinbar);
|
|
}
|
|
|
|
// 重置处理状态(当新K线开始时)
|
|
static datetime lastBarTime = 0;
|
|
datetime currentBarTime = iTime(_Symbol, _Period, 0);
|
|
|
|
if(currentBarTime != lastBarTime) {
|
|
isPinbarProcessed = false;
|
|
lastBarTime = currentBarTime;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit() {
|
|
Print("=== Pinbar策略EA初始化 ===");
|
|
Print("Pinbar识别参数 - 实体比例:", PinbarBodyRatio, " 影线比例:", PinbarShadowRatio, " 最小长度:", MinPinbarLength);
|
|
Print("交易方向 - 多单:", (EnableBuy ? "启用" : "禁用"), " 空单:", (EnableSell ? "启用" : "禁用"));
|
|
Print("手数模式:", (EnableDynamicLots ? "动态手数" : "固定手数"));
|
|
if(EnableDynamicLots) {
|
|
Print("基准余额:", BaseBalance, " 基准手数:", StartLots);
|
|
} else {
|
|
Print("固定手数:", StartLots);
|
|
}
|
|
Print("ATR过滤:", (EnableATRFilter ? "启用" : "禁用"));
|
|
if(EnableATRFilter) {
|
|
Print("ATR周期:", ATRPeriod, " ATR阈值:", ATRThreshold);
|
|
}
|
|
Print("交易量过滤:", (EnableVolumeFilter ? "启用" : "禁用"));
|
|
if(EnableVolumeFilter) {
|
|
Print("交易量增加比例阈值:", VolumeIncreaseRatio * 100, "%");
|
|
}
|
|
Print("当前服务器时间: ", TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason) {
|
|
if(atrHandle != INVALID_HANDLE)
|
|
IndicatorRelease(atrHandle);
|
|
Print("Pinbar策略EA已停止,原因代码:", reason);
|
|
}
|