mql5/Experts/Pinbar_Strategy.mq5
1Morty 50a3adba89
2025-09-08 21:00:32 +08:00

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);
}