Files
zyb-ea/IC-15024172/20260516_Breakout.mq5
zhangyangbin 4e7c7c7a50 feat: 突破
2026-05-25 11:59:07 +08:00

1080 lines
43 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| 20260516_Trade.mq5 |
//| 突破交易策略 - 完整交易版本 |
//+------------------------------------------------------------------+
#property copyright "Breakout Strategy"
#property version "1.01"
#property strict
#include <Trade\Trade.mqh>
// 手数计算方式
enum ENUM_LOT_SIZE_MODE {
LOT_SIZE_FIXED = 0, // 固定手数
LOT_SIZE_RISK_PERCENT = 1 // 结余风险比例(RiskPercent)
};
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
input group "=== 波段识别参数 ==="
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // K线周期
input int InpMAPeriod = 14; // MA周期
input double InpMinWavePercent = 0.1; // 最小波段阈值百分比(%)
input double InpMaxWavePercent = 10.0; // 最大波段阈值百分比(%)
input double InpPullbackTolerance = 0.0; // 反向突破容忍度(%) 0=不容忍
input int InpMinWaveBars = 3; // 有效波段最少K线数(含两端极值所在K)
input group "=== 风险管理参数 ==="
input int InpStopLossPoints = 200; // 止损点数
input int InpTakeProfitPoints = 300; // 止盈点数
input bool InpUseTrailingStop = true; // 使用移动止损
input group "=== 仓位管理参数 ==="
input ENUM_LOT_SIZE_MODE InpLotSizeMode = LOT_SIZE_RISK_PERCENT; // 手数模式
input double InpFixedLots = 0.01; // 固定手数(固定模式)
input double InpRiskPercent = 5.0; // 每笔风险占结余%(风险比例模式)
input double InpMaxLots = 99.0; // 单笔最大手数(0=仅受品种限制)
input int InpMaxPositions = 99; // 最大持仓笔数
input bool InpOnePositionPerDirection = true; // 单方向最多持有一单
input group "=== 连续亏损保护 ==="
input int InpConsecutiveLosses = 3; // 连续亏损次数触发冷冻(0=禁用)
input int InpFreezeBarCount = 60; // 冷冻K线根数(0=禁用)
input group "=== 调试选项 ==="
input bool InpShowDebugInfo = false; // 显示调试信息
input bool InpShowMarkers = true; // 显示极值点标记
input int InpMagicNumber = 20260516; // EA魔术号
input bool InpCloseManualOrders = true; // 禁止手工单(自动平掉)
//+------------------------------------------------------------------+
//| 全局变量 |
//+------------------------------------------------------------------+
int ma_handle; // MA指标句柄
CTrade trade; // 交易对象
// 最新有效波段信息
struct ValidWaveInfo {
bool exists; // 是否存在有效波段
double high_price; // 高点价格
double low_price; // 低点价格
datetime update_time; // 更新时间
bool high_used; // 高点是否已使用
bool low_used; // 低点是否已使用
};
ValidWaveInfo latest_wave; // 最新有效波段
double g_sync_wave_high = 0.0; // 已挂单的波段高价(用于检测换波段)
double g_sync_wave_low = 0.0;
// 连续亏损保护相关变量
int consecutive_loss_count = 0; // 连续亏损计数器
datetime freeze_until_time = 0; // 冷冻结束时间(0表示未冷冻)
int freeze_bar_index = 0; // 冷冻起始K线索引
// 极值点结构体定义
struct ExtremePoint {
datetime time;
double price;
int type;
bool is_valid;
};
// 函数声明
void UpdateLatestValidWave();
int CountBarsBetweenExtremeTimes(const MqlRates &rates[], const datetime t1, const datetime t2);
bool IsValidWaveByBarCount(const MqlRates &rates[], const datetime t1, const datetime t2);
void DrawExtremeMarkers(ExtremePoint &extremes[]);
void DrawLatestValidWave(double high_price, datetime high_time, double low_price, datetime low_time);
int CheckBreakout(int index, const MqlRates &rates[], const double &ma[]);
void FilterBreakouts(const int &breakout_bars[], const int &breakout_types[],
const MqlRates &rates[], int &filtered_bars[], int &filtered_types[]);
void SyncBreakoutPendingOrders();
void CancelEaPendingOrders(const bool cancel_buy_stop, const bool cancel_sell_stop);
ulong FindEaPendingOrder(const ENUM_ORDER_TYPE order_type);
bool CalcPendingSlTp(const ENUM_ORDER_TYPE pending_type, const double trigger_price, double &sl, double &tp);
bool PlaceBreakoutPendingOrder(const ENUM_ORDER_TYPE pending_type, const double trigger_price,
const double wave_high, const double wave_low);
double GetStopLossOffset();
double GetTakeProfitOffset();
double StopLossAmountFromPositionComment(const string &comment);
double CalculateLotSize(const double wave_high, const double wave_low);
double NormalizeVolumeLots(double lots);
void ManagePositions();
void CheckTrailingStop(ulong ticket);
void CheckAndCloseManualOrders();
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 创建MA指标(使用指定的K线周期)
ma_handle = iMA(_Symbol, InpTimeframe, InpMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
if(ma_handle == INVALID_HANDLE) {
Print("创建MA指标失败");
return(INIT_FAILED);
}
// 设置交易参数
trade.SetExpertMagicNumber(InpMagicNumber);
trade.SetDeviationInPoints(10);
trade.SetTypeFilling(ORDER_FILLING_IOC);
// 初始化最新有效波段
latest_wave.exists = false;
latest_wave.high_price = 0;
latest_wave.low_price = 0;
latest_wave.update_time = 0;
latest_wave.high_used = false;
latest_wave.low_used = false;
Print("========================================");
Print("突破交易策略EA初始化成功");
Print("品种:", _Symbol);
Print("K线周期:", EnumToString(InpTimeframe));
Print("MA周期:", InpMAPeriod);
Print("波段阈值范围: ", InpMinWavePercent, "% - ", InpMaxWavePercent, "%");
Print("有效波段最少K线: ", InpMinWaveBars, " (两极值间含两端,<=1=不限制)");
if(InpPullbackTolerance > 0)
Print("反向突破容忍度: ", DoubleToString(InpPullbackTolerance, 1), "% (启用)");
else
Print("反向突破容忍度: 0% (禁用 - 保持原有逻辑)");
Print("止损:", InpStopLossPoints, "点 | 止盈:", InpTakeProfitPoints, "点");
Print("移动止损:", (InpUseTrailingStop ? "启用" : "禁用"));
Print("最大持仓笔数:", InpMaxPositions, " 单笔最大手数:", InpMaxLots);
Print("单方向持仓限制:", (InpOnePositionPerDirection ? "启用 (每方向最多1单)" : "禁用"));
if(InpConsecutiveLosses > 0 && InpFreezeBarCount > 0)
Print("连续亏损保护: 启用 (", InpConsecutiveLosses, "次亏损→冷冻", InpFreezeBarCount, "根K线)");
else
Print("连续亏损保护: 禁用");
if(InpLotSizeMode == LOT_SIZE_RISK_PERCENT)
Print("手数模式: 结余风险比例 ", InpRiskPercent, "% (按止损距离反推手数)");
else
Print("手数模式: 固定手数 ", InpFixedLots);
Print("开仓方式: 突破挂单 (高点BUY STOP / 低点SELL STOP)");
Print("禁止手工单:", (InpCloseManualOrders ? "启用 (自动平掉手工单)" : "禁用"));
Print("========================================");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(ma_handle != INVALID_HANDLE)
IndicatorRelease(ma_handle);
// 删除所有标记
ObjectsDeleteAll(0, "ValidWave_");
CancelEaPendingOrders(true, true);
Print("突破交易策略EA已卸载");
}
//+------------------------------------------------------------------+
//| 交易事务处理函数 - 用于检测亏损单 |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
const MqlTradeRequest& request,
const MqlTradeResult& result)
{
if(trans.type == TRADE_TRANSACTION_DEAL_ADD) {
if(HistoryDealSelect(trans.deal)) {
if(HistoryDealGetString(trans.deal, DEAL_SYMBOL) == _Symbol &&
HistoryDealGetInteger(trans.deal, DEAL_MAGIC) == InpMagicNumber &&
HistoryDealGetInteger(trans.deal, DEAL_ENTRY) == DEAL_ENTRY_IN) {
const ENUM_DEAL_TYPE deal_type =
(ENUM_DEAL_TYPE)HistoryDealGetInteger(trans.deal, DEAL_TYPE);
if(deal_type == DEAL_TYPE_BUY)
latest_wave.high_used = true;
else if(deal_type == DEAL_TYPE_SELL)
latest_wave.low_used = true;
}
}
}
// 只在功能启用时处理连续亏损
if(InpConsecutiveLosses <= 0 || InpFreezeBarCount <= 0)
return;
// 只处理订单成交事件
if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
return;
// 需要先选择历史记录
if(!HistorySelect(0, TimeCurrent()))
return;
// 获取最新的Deal
int total_deals = HistoryDealsTotal();
if(total_deals <= 0)
return;
ulong deal_ticket = HistoryDealGetTicket(total_deals - 1);
if(deal_ticket == 0)
return;
// 检查魔术号
long deal_magic = HistoryDealGetInteger(deal_ticket, DEAL_MAGIC);
if(deal_magic != InpMagicNumber)
return;
// 检查是否是平仓交易
ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
if(entry != DEAL_ENTRY_OUT)
return;
// 获取交易详情
double profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);
double commission = HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION);
double swap = HistoryDealGetDouble(deal_ticket, DEAL_SWAP);
double net_profit = profit + commission + swap;
// 判断是亏损还是盈利
if(net_profit < 0)
{
// 亏损:增加计数器
consecutive_loss_count++;
Print("【连续亏损保护】亏损 ", consecutive_loss_count, "/", InpConsecutiveLosses,
" | 净亏损: $", DoubleToString(net_profit, 2));
// 检查是否达到冷冻阈值
if(consecutive_loss_count >= InpConsecutiveLosses)
{
// 进入冷冻期
freeze_bar_index = Bars(_Symbol, InpTimeframe) + InpFreezeBarCount;
freeze_until_time = TimeCurrent();
Print("!!! 触发交易冷冻 !!! 冷冻", InpFreezeBarCount, "根K线");
}
}
else if(net_profit > 0)
{
// 盈利:重置计数器
if(consecutive_loss_count > 0)
{
Print("【连续亏损保护】盈利 - 计数器重置: ", consecutive_loss_count, " → 0 | 净盈利: $", DoubleToString(net_profit, 2));
consecutive_loss_count = 0;
}
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. 检查并关闭手工单(如果启用)
if(InpCloseManualOrders)
CheckAndCloseManualOrders();
// 2. 更新最新有效波段
UpdateLatestValidWave();
// 3. 同步突破挂单(BUY STOP@高 / SELL STOP@低)
SyncBreakoutPendingOrders();
// 4. 管理已有持仓
ManagePositions();
}
//+------------------------------------------------------------------+
//| 更新最新有效波段 |
//+------------------------------------------------------------------+
int CountBarsBetweenExtremeTimes(const MqlRates &rates[], const datetime t1, const datetime t2)
{
const datetime t_lo = (t1 <= t2) ? t1 : t2;
const datetime t_hi = (t1 >= t2) ? t1 : t2;
int count = 0;
const int n = ArraySize(rates);
for(int j = 0; j < n; j++) {
if(rates[j].time >= t_lo && rates[j].time <= t_hi)
count++;
}
return count;
}
bool IsValidWaveByBarCount(const MqlRates &rates[], const datetime t1, const datetime t2)
{
if(InpMinWaveBars <= 1)
return true;
return (CountBarsBetweenExtremeTimes(rates, t1, t2) >= InpMinWaveBars);
}
void UpdateLatestValidWave()
{
int bars = Bars(_Symbol, InpTimeframe);
if(bars < InpMAPeriod + 2)
return;
// 限制处理的K线数量
int process_bars = MathMin(bars, 500);
// 获取价格数据(使用指定的K线周期)
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(_Symbol, InpTimeframe, 0, process_bars, rates) <= 0)
return;
// 获取MA数据
double ma_array[];
ArraySetAsSeries(ma_array, true);
if(CopyBuffer(ma_handle, 0, 0, process_bars, ma_array) <= 0)
return;
// 识别突破K线
int breakout_bars[];
int breakout_types[];
ArrayResize(breakout_bars, 0);
ArrayResize(breakout_types, 0);
for(int i = process_bars - InpMAPeriod - 1; i >= 1; i--) {
int breakout_type = CheckBreakout(i, rates, ma_array);
if(breakout_type != 0) {
int size = ArraySize(breakout_bars);
ArrayResize(breakout_bars, size + 1);
ArrayResize(breakout_types, size + 1);
breakout_bars[size] = i;
breakout_types[size] = breakout_type;
}
}
// 过滤连续同向突破
int filtered_bars[];
int filtered_types[];
FilterBreakouts(breakout_bars, breakout_types, rates, filtered_bars, filtered_types);
// 计算极值点(支持反向突破容忍度)
ExtremePoint extremes[];
ArrayResize(extremes, 0);
// 如果容忍度为0,使用原有逻辑(相邻突破K线之间的极值)
if(InpPullbackTolerance <= 0.0)
{
for(int i = 0; i < ArraySize(filtered_bars) - 1; i++) {
int current_bar = filtered_bars[i];
int current_type = filtered_types[i];
int next_bar = filtered_bars[i + 1];
double extreme_price = 0;
datetime extreme_time = 0;
if(current_type == 1) {
extreme_price = rates[current_bar].high;
extreme_time = rates[current_bar].time;
for(int j = current_bar; j >= next_bar; j--) {
if(rates[j].high > extreme_price) {
extreme_price = rates[j].high;
extreme_time = rates[j].time;
}
}
} else {
extreme_price = rates[current_bar].low;
extreme_time = rates[current_bar].time;
for(int j = current_bar; j >= next_bar; j--) {
if(rates[j].low < extreme_price) {
extreme_price = rates[j].low;
extreme_time = rates[j].time;
}
}
}
int size = ArraySize(extremes);
ArrayResize(extremes, size + 1);
extremes[size].time = extreme_time;
extremes[size].price = extreme_price;
extremes[size].type = current_type;
extremes[size].is_valid = false;
}
}
else
{
// 容忍模式:跨越反向突破K线计算波段
for(int i = 0; i < ArraySize(filtered_bars); i++) {
int start_bar = filtered_bars[i];
int start_type = filtered_types[i];
double wave_high = rates[start_bar].high;
double wave_low = rates[start_bar].low;
datetime wave_high_time = rates[start_bar].time;
datetime wave_low_time = rates[start_bar].time;
int end_bar = 0;
bool wave_terminated = false;
// 向后扫描,直到遇到不可容忍的反向突破
for(int j = i + 1; j < ArraySize(filtered_bars); j++) {
int current_bar = filtered_bars[j];
int current_type = filtered_types[j];
// 更新波段的高低点
if(rates[current_bar].high > wave_high) {
wave_high = rates[current_bar].high;
wave_high_time = rates[current_bar].time;
}
if(rates[current_bar].low < wave_low) {
wave_low = rates[current_bar].low;
wave_low_time = rates[current_bar].time;
}
// 检查中间所有K线的极值
int prev_bar = (j > 0) ? filtered_bars[j-1] : start_bar;
for(int k = prev_bar; k >= current_bar; k--) {
if(rates[k].high > wave_high) {
wave_high = rates[k].high;
wave_high_time = rates[k].time;
}
if(rates[k].low < wave_low) {
wave_low = rates[k].low;
wave_low_time = rates[k].time;
}
}
// 如果遇到反向突破K线,检查回撤是否可容忍
if(current_type != start_type) {
double wave_range = wave_high - wave_low;
double pullback_percent = 0;
if(start_type == 1) {
// 多头波段遇到空单突破K线
pullback_percent = ((wave_high - rates[current_bar].close) / wave_range) * 100.0;
} else {
// 空头波段遇到多单突破K线
pullback_percent = ((rates[current_bar].close - wave_low) / wave_range) * 100.0;
}
if(pullback_percent > InpPullbackTolerance) {
// 回撤超过容忍度,终止波段
end_bar = current_bar;
wave_terminated = true;
break;
}
// 否则继续,忽略此反向突破
}
}
// 添加极值点
if(start_type == 1) {
// 多头波段:先低点后高点
int size = ArraySize(extremes);
ArrayResize(extremes, size + 1);
extremes[size].time = wave_low_time;
extremes[size].price = wave_low;
extremes[size].type = -1; // 低点
extremes[size].is_valid = false;
ArrayResize(extremes, size + 2);
extremes[size + 1].time = wave_high_time;
extremes[size + 1].price = wave_high;
extremes[size + 1].type = 1; // 高点
extremes[size + 1].is_valid = false;
} else {
// 空头波段:先高点后低点
int size = ArraySize(extremes);
ArrayResize(extremes, size + 1);
extremes[size].time = wave_high_time;
extremes[size].price = wave_high;
extremes[size].type = 1; // 高点
extremes[size].is_valid = false;
ArrayResize(extremes, size + 2);
extremes[size + 1].time = wave_low_time;
extremes[size + 1].price = wave_low;
extremes[size + 1].type = -1; // 低点
extremes[size + 1].is_valid = false;
}
// 如果波段被终止,跳到终止点继续
if(wave_terminated) {
// 找到end_bar在filtered_bars中的索引
for(int k = i + 1; k < ArraySize(filtered_bars); k++) {
if(filtered_bars[k] == end_bar) {
i = k - 1; // -1因为循环会++
break;
}
}
} else {
// 波段延续到最后
break;
}
}
}
// 判断有效波段并标记
for(int i = 1; i < ArraySize(extremes); i++) {
double price_diff = MathAbs(extremes[i].price - extremes[i-1].price);
double price_diff_points = price_diff / _Point;
// 计算阈值(百分比模式:以前一个极值点价格为基准计算百分比)
double base_price = extremes[i-1].price;
double min_threshold = (base_price * InpMinWavePercent / 100.0) / _Point;
double max_threshold = (base_price * InpMaxWavePercent / 100.0) / _Point;
// 波段必须在最小和最大阈值之间才是有效波段
if(price_diff_points >= min_threshold && price_diff_points <= max_threshold &&
IsValidWaveByBarCount(rates, extremes[i - 1].time, extremes[i].time)) {
extremes[i-1].is_valid = true;
extremes[i].is_valid = true;
}
}
// 绘制所有极值点标记
if(InpShowMarkers) {
DrawExtremeMarkers(extremes);
}
// 查找最新的有效波段
for(int i = ArraySize(extremes) - 1; i >= 1; i--) {
double price_diff = MathAbs(extremes[i].price - extremes[i-1].price);
double price_diff_points = price_diff / _Point;
// 计算阈值(百分比模式)
double base_price = extremes[i-1].price;
double min_threshold = (base_price * InpMinWavePercent / 100.0) / _Point;
double max_threshold = (base_price * InpMaxWavePercent / 100.0) / _Point;
// 波段必须在最小和最大阈值之间才是有效波段
if(price_diff_points >= min_threshold && price_diff_points <= max_threshold &&
IsValidWaveByBarCount(rates, extremes[i - 1].time, extremes[i].time)) {
// 找到最新的有效波段
double high = MathMax(extremes[i].price, extremes[i-1].price);
double low = MathMin(extremes[i].price, extremes[i-1].price);
datetime high_time = (extremes[i].price > extremes[i-1].price) ? extremes[i].time : extremes[i-1].time;
datetime low_time = (extremes[i].price < extremes[i-1].price) ? extremes[i].time : extremes[i-1].time;
// 检查是否是新的波段
if(latest_wave.exists == false ||
extremes[i].time > latest_wave.update_time ||
high != latest_wave.high_price ||
low != latest_wave.low_price) {
latest_wave.exists = true;
latest_wave.high_price = high;
latest_wave.low_price = low;
latest_wave.update_time = extremes[i].time;
latest_wave.high_used = false; // 新波段,重置使用状态
latest_wave.low_used = false;
// 绘制最新有效波段
if(InpShowMarkers) {
DrawLatestValidWave(high, high_time, low, low_time);
}
if(InpShowDebugInfo) {
Print("更新最新有效波段 - 高:", DoubleToString(high, _Digits),
" 低:", DoubleToString(low, _Digits),
" 价差:", (int)price_diff_points, "点",
" K线数:", CountBarsBetweenExtremeTimes(rates, extremes[i - 1].time, extremes[i].time),
" 阈值范围:", StringFormat("%.2f%%-%.2f%% (%.0f-%.0f点)",
InpMinWavePercent, InpMaxWavePercent, min_threshold, max_threshold));
}
}
break;
}
}
}
//+------------------------------------------------------------------+
//| 绘制所有极值点标记 |
//+------------------------------------------------------------------+
void DrawExtremeMarkers(ExtremePoint &extremes[])
{
// 删除旧的标记
ObjectsDeleteAll(0, "ValidWave_Extreme_");
for(int i = 0; i < ArraySize(extremes); i++) {
string obj_name = "ValidWave_Extreme_" + IntegerToString(i);
if(extremes[i].type == 1) {
// 高点 - 画下箭头
ObjectCreate(0, obj_name, OBJ_ARROW, 0, extremes[i].time, extremes[i].price);
ObjectSetInteger(0, obj_name, OBJPROP_ARROWCODE, 234);
ObjectSetInteger(0, obj_name, OBJPROP_COLOR, extremes[i].is_valid ? clrRed : clrDarkRed);
ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, extremes[i].is_valid ? 3 : 1);
ObjectSetInteger(0, obj_name, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
} else {
// 低点 - 画上箭头
ObjectCreate(0, obj_name, OBJ_ARROW, 0, extremes[i].time, extremes[i].price);
ObjectSetInteger(0, obj_name, OBJPROP_ARROWCODE, 233);
ObjectSetInteger(0, obj_name, OBJPROP_COLOR, extremes[i].is_valid ? clrLime : clrDarkGreen);
ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, extremes[i].is_valid ? 3 : 1);
ObjectSetInteger(0, obj_name, OBJPROP_ANCHOR, ANCHOR_TOP);
}
}
}
//+------------------------------------------------------------------+
//| 绘制最新有效波段 |
//+------------------------------------------------------------------+
void DrawLatestValidWave(double high_price, datetime high_time, double low_price, datetime low_time)
{
// 删除旧的最新波段标记
ObjectDelete(0, "ValidWave_Latest_High");
ObjectDelete(0, "ValidWave_Latest_Low");
ObjectDelete(0, "ValidWave_Latest_Line");
// 标记最新有效波段的高点(更大更亮的箭头)
ObjectCreate(0, "ValidWave_Latest_High", OBJ_ARROW, 0, high_time, high_price);
ObjectSetInteger(0, "ValidWave_Latest_High", OBJPROP_ARROWCODE, 234);
ObjectSetInteger(0, "ValidWave_Latest_High", OBJPROP_COLOR, clrYellow);
ObjectSetInteger(0, "ValidWave_Latest_High", OBJPROP_WIDTH, 4);
ObjectSetInteger(0, "ValidWave_Latest_High", OBJPROP_ANCHOR, ANCHOR_BOTTOM);
// 标记最新有效波段的低点
ObjectCreate(0, "ValidWave_Latest_Low", OBJ_ARROW, 0, low_time, low_price);
ObjectSetInteger(0, "ValidWave_Latest_Low", OBJPROP_ARROWCODE, 233);
ObjectSetInteger(0, "ValidWave_Latest_Low", OBJPROP_COLOR, clrYellow);
ObjectSetInteger(0, "ValidWave_Latest_Low", OBJPROP_WIDTH, 4);
ObjectSetInteger(0, "ValidWave_Latest_Low", OBJPROP_ANCHOR, ANCHOR_TOP);
// 绘制连接线
ObjectCreate(0, "ValidWave_Latest_Line", OBJ_TREND, 0, high_time, high_price, low_time, low_price);
ObjectSetInteger(0, "ValidWave_Latest_Line", OBJPROP_COLOR, clrYellow);
ObjectSetInteger(0, "ValidWave_Latest_Line", OBJPROP_WIDTH, 2);
ObjectSetInteger(0, "ValidWave_Latest_Line", OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, "ValidWave_Latest_Line", OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, "ValidWave_Latest_Line", OBJPROP_BACK, true);
}
//+------------------------------------------------------------------+
//| 检查是否是突破K线 |
//+------------------------------------------------------------------+
int CheckBreakout(int index, const MqlRates &rates[], const double &ma[])
{
if(rates[index].open < ma[index] && rates[index].close > ma[index])
return 1;
if(rates[index].open > ma[index] && rates[index].close < ma[index])
return -1;
return 0;
}
//+------------------------------------------------------------------+
//| 过滤连续同向突破 |
//+------------------------------------------------------------------+
void FilterBreakouts(const int &breakout_bars[], const int &breakout_types[],
const MqlRates &rates[], int &filtered_bars[], int &filtered_types[])
{
int total = ArraySize(breakout_bars);
ArrayResize(filtered_bars, 0);
ArrayResize(filtered_types, 0);
for(int i = 0; i < total; i++) {
int current_bar = breakout_bars[i];
int current_type = breakout_types[i];
bool skip = false;
for(int j = i + 1; j < total; j++) {
if(breakout_types[j] != current_type)
break;
if(current_type == 1) {
if(rates[breakout_bars[j]].low < rates[current_bar].low) {
skip = true;
break;
}
} else {
if(rates[breakout_bars[j]].high > rates[current_bar].high) {
skip = true;
break;
}
}
}
if(!skip) {
int size = ArraySize(filtered_bars);
ArrayResize(filtered_bars, size + 1);
ArrayResize(filtered_types, size + 1);
filtered_bars[size] = current_bar;
filtered_types[size] = current_type;
}
}
}
//+------------------------------------------------------------------+
//| 突破挂单管理 |
//+------------------------------------------------------------------+
void CancelEaPendingOrders(const bool cancel_buy_stop, const bool cancel_sell_stop)
{
for(int i = OrdersTotal() - 1; i >= 0; i--) {
const ulong ticket = OrderGetTicket(i);
if(ticket == 0 || !OrderSelect(ticket))
continue;
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
continue;
if(OrderGetInteger(ORDER_MAGIC) != InpMagicNumber)
continue;
const ENUM_ORDER_TYPE ot = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
if(cancel_buy_stop && ot == ORDER_TYPE_BUY_STOP)
trade.OrderDelete(ticket);
else if(cancel_sell_stop && ot == ORDER_TYPE_SELL_STOP)
trade.OrderDelete(ticket);
}
}
ulong FindEaPendingOrder(const ENUM_ORDER_TYPE order_type)
{
for(int i = OrdersTotal() - 1; i >= 0; i--) {
const ulong ticket = OrderGetTicket(i);
if(ticket == 0 || !OrderSelect(ticket))
continue;
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
continue;
if(OrderGetInteger(ORDER_MAGIC) != InpMagicNumber)
continue;
if((ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE) == order_type)
return ticket;
}
return 0;
}
bool CalcPendingSlTp(const ENUM_ORDER_TYPE pending_type, const double trigger_price, double &sl, double &tp)
{
const double stop_loss_amount = GetStopLossOffset();
const double take_profit_amount = GetTakeProfitOffset();
const int stops_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
const double min_stop_distance = stops_level * _Point;
sl = 0.0;
tp = 0.0;
if(pending_type == ORDER_TYPE_BUY_STOP) {
sl = NormalizeDouble(trigger_price - stop_loss_amount, _Digits);
tp = NormalizeDouble(trigger_price + take_profit_amount, _Digits);
if(stops_level > 0) {
if(trigger_price - sl < min_stop_distance)
sl = NormalizeDouble(trigger_price - min_stop_distance, _Digits);
if(tp - trigger_price < min_stop_distance)
tp = NormalizeDouble(trigger_price + min_stop_distance, _Digits);
}
} else if(pending_type == ORDER_TYPE_SELL_STOP) {
sl = NormalizeDouble(trigger_price + stop_loss_amount, _Digits);
tp = NormalizeDouble(trigger_price - take_profit_amount, _Digits);
if(stops_level > 0) {
if(sl - trigger_price < min_stop_distance)
sl = NormalizeDouble(trigger_price + min_stop_distance, _Digits);
if(trigger_price - tp < min_stop_distance)
tp = NormalizeDouble(trigger_price - min_stop_distance, _Digits);
}
} else {
return false;
}
return true;
}
bool PlaceBreakoutPendingOrder(const ENUM_ORDER_TYPE pending_type, const double trigger_price,
const double wave_high, const double wave_low)
{
const double lots = CalculateLotSize(wave_high, wave_low);
if(lots <= 0.0)
return false;
double sl = 0.0, tp = 0.0;
if(!CalcPendingSlTp(pending_type, trigger_price, sl, tp))
return false;
const int wave_range_points = (int)MathRound(MathAbs(wave_high - wave_low) / _Point);
const string comment = StringFormat("WR%d", wave_range_points);
bool result = false;
if(pending_type == ORDER_TYPE_BUY_STOP)
result = trade.BuyStop(lots, trigger_price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, comment);
else if(pending_type == ORDER_TYPE_SELL_STOP)
result = trade.SellStop(lots, trigger_price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, comment);
if(!result) {
Print("挂单失败 ", EnumToString(pending_type), " 触发价:", trigger_price,
" 错误:", trade.ResultRetcode(), " - ", trade.ResultRetcodeDescription());
return false;
}
Print("突破挂单成功 ", EnumToString(pending_type),
" 触发:", trigger_price, " 手数:", lots,
" SL:", sl, " TP:", tp,
" 波段 H:", wave_high, " L:", wave_low);
return true;
}
void SyncBreakoutPendingOrders()
{
if(!latest_wave.exists) {
CancelEaPendingOrders(true, true);
g_sync_wave_high = 0.0;
g_sync_wave_low = 0.0;
return;
}
if(InpConsecutiveLosses > 0 && InpFreezeBarCount > 0 && freeze_bar_index > 0) {
const int current_bars = Bars(_Symbol, InpTimeframe);
if(current_bars < freeze_bar_index) {
CancelEaPendingOrders(true, true);
return;
}
if(freeze_bar_index > 0) {
Print("【连续亏损保护】冷冻解除,恢复交易");
consecutive_loss_count = 0;
freeze_bar_index = 0;
freeze_until_time = 0;
}
}
if(MathAbs(latest_wave.high_price - g_sync_wave_high) > _Point * 0.5 ||
MathAbs(latest_wave.low_price - g_sync_wave_low) > _Point * 0.5) {
CancelEaPendingOrders(true, true);
g_sync_wave_high = latest_wave.high_price;
g_sync_wave_low = latest_wave.low_price;
}
int total_positions = 0;
int buy_positions = 0;
int sell_positions = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(!PositionSelectByTicket(PositionGetTicket(i)))
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
total_positions++;
const ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(pos_type == POSITION_TYPE_BUY)
buy_positions++;
else if(pos_type == POSITION_TYPE_SELL)
sell_positions++;
}
if(total_positions >= InpMaxPositions) {
CancelEaPendingOrders(true, true);
if(InpShowDebugInfo)
Print("已达最大持仓笔数,撤销突破挂单: ", total_positions, "/", InpMaxPositions);
return;
}
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
const double wave_high = latest_wave.high_price;
const double wave_low = latest_wave.low_price;
const double lots = CalculateLotSize(wave_high, wave_low);
// 高点 BUY STOP
if(!latest_wave.high_used && (!InpOnePositionPerDirection || buy_positions < 1)) {
if(ask >= wave_high - _Point * 0.5) {
CancelEaPendingOrders(true, false);
if(InpShowDebugInfo)
Print("价格已越过波段高点,暂不挂BUY STOP Ask:", ask, " 高:", wave_high);
} else {
ulong ticket = FindEaPendingOrder(ORDER_TYPE_BUY_STOP);
if(ticket > 0 && OrderSelect(ticket)) {
const double order_price = OrderGetDouble(ORDER_PRICE_OPEN);
const double order_lots = OrderGetDouble(ORDER_VOLUME_CURRENT);
if(MathAbs(order_price - wave_high) > _Point * 0.5 ||
MathAbs(order_lots - lots) > 1e-8) {
trade.OrderDelete(ticket);
ticket = 0;
}
}
if(ticket == 0)
PlaceBreakoutPendingOrder(ORDER_TYPE_BUY_STOP, wave_high, wave_high, wave_low);
}
} else {
CancelEaPendingOrders(true, false);
}
// 低点 SELL STOP
if(!latest_wave.low_used && (!InpOnePositionPerDirection || sell_positions < 1)) {
if(bid <= wave_low + _Point * 0.5) {
CancelEaPendingOrders(false, true);
if(InpShowDebugInfo)
Print("价格已越过波段低点,暂不挂SELL STOP Bid:", bid, " 低:", wave_low);
} else {
ulong ticket = FindEaPendingOrder(ORDER_TYPE_SELL_STOP);
if(ticket > 0 && OrderSelect(ticket)) {
const double order_price = OrderGetDouble(ORDER_PRICE_OPEN);
const double order_lots = OrderGetDouble(ORDER_VOLUME_CURRENT);
if(MathAbs(order_price - wave_low) > _Point * 0.5 ||
MathAbs(order_lots - lots) > 1e-8) {
trade.OrderDelete(ticket);
ticket = 0;
}
}
if(ticket == 0)
PlaceBreakoutPendingOrder(ORDER_TYPE_SELL_STOP, wave_low, wave_high, wave_low);
}
} else {
CancelEaPendingOrders(false, true);
}
}
//+------------------------------------------------------------------+
//| 止损/止盈距离(点数) |
//+------------------------------------------------------------------+
double GetStopLossOffset()
{
if(InpStopLossPoints <= 0)
return 0.0;
return (double)InpStopLossPoints * _Point;
}
double GetTakeProfitOffset()
{
if(InpTakeProfitPoints <= 0)
return 0.0;
return (double)InpTakeProfitPoints * _Point;
}
double StopLossAmountFromPositionComment(const string &comment)
{
return GetStopLossOffset();
}
//+------------------------------------------------------------------+
//| 手数规范化 |
//+------------------------------------------------------------------+
double NormalizeVolumeLots(double lots)
{
const double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
const double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
const double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(InpMaxLots > 0.0)
lots = MathMin(lots, InpMaxLots);
if(lots < min_lot)
lots = min_lot;
if(lots > max_lot)
lots = max_lot;
if(lot_step > 0.0)
lots = MathFloor(lots / lot_step) * lot_step;
return lots;
}
//+------------------------------------------------------------------+
//| 计算开仓手数 |
//+------------------------------------------------------------------+
double CalculateLotSize(const double wave_high, const double wave_low)
{
double lots = InpFixedLots;
if(InpLotSizeMode == LOT_SIZE_RISK_PERCENT && InpRiskPercent > 0.0) {
const double stop_loss_dist = GetStopLossOffset();
const double balance = AccountInfoDouble(ACCOUNT_BALANCE);
const double risk_money = balance * InpRiskPercent / 100.0;
const double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
const double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(stop_loss_dist > 0.0 && tick_size > 0.0 && tick_value > 0.0 && risk_money > 0.0) {
const double loss_per_lot = (stop_loss_dist / tick_size) * tick_value;
if(loss_per_lot > 0.0)
lots = risk_money / loss_per_lot;
}
}
return NormalizeVolumeLots(lots);
}
//+------------------------------------------------------------------+
//| 管理持仓 |
//+------------------------------------------------------------------+
void ManagePositions()
{
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(!PositionSelectByTicket(PositionGetTicket(i)))
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
// 检查移动止损
ulong ticket = PositionGetTicket(i);
CheckTrailingStop(ticket);
}
}
//+------------------------------------------------------------------+
//| 检查移动止损 |
//+------------------------------------------------------------------+
void CheckTrailingStop(ulong ticket)
{
// 如果未开启移动止损,直接返回
if(!InpUseTrailingStop)
return;
if(!PositionSelectByTicket(ticket))
return;
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double current_sl = PositionGetDouble(POSITION_SL);
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double current_price = (pos_type == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
// 计算止损金额(有效波段区间 × 比例,备注中 WR 存区间点数)
const string comment = PositionGetString(POSITION_COMMENT);
const double stop_loss_amount = StopLossAmountFromPositionComment(comment);
// 计算浮盈
double profit_amount = 0;
if(pos_type == POSITION_TYPE_BUY) {
profit_amount = current_price - open_price;
} else {
profit_amount = open_price - current_price;
}
// 浮盈达到止损金额,移动止损至成本价
if(profit_amount >= stop_loss_amount) {
double new_sl = open_price;
// 检查是否需要更新
bool need_update = false;
if(pos_type == POSITION_TYPE_BUY && (current_sl < new_sl || current_sl == 0)) {
need_update = true;
} else if(pos_type == POSITION_TYPE_SELL && (current_sl > new_sl || current_sl == 0)) {
need_update = true;
}
if(need_update) {
double tp = PositionGetDouble(POSITION_TP);
if(trade.PositionModify(ticket, new_sl, tp)) {
Print("移动止损至成本价 - Ticket:", ticket, " 新止损:", new_sl);
}
}
}
}
//+------------------------------------------------------------------+
//| 检查并关闭手工单 |
//+------------------------------------------------------------------+
void CheckAndCloseManualOrders()
{
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(!PositionSelectByTicket(PositionGetTicket(i)))
continue;
// 只处理本品种
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
// 检查magic number0表示手工单
long magic = PositionGetInteger(POSITION_MAGIC);
if(magic == 0) {
ulong ticket = PositionGetTicket(i);
// 平仓手工单
if(trade.PositionClose(ticket)) {
Print("【禁止手工单】已平掉手工单 - Ticket:", ticket,
" 类型:", (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY ? "多单" : "空单"));
} else {
Print("【禁止手工单】平仓失败 - Ticket:", ticket, " 错误:", GetLastError());
}
}
}
}