feat: box

This commit is contained in:
zhangyangbin
2026-05-30 10:42:53 +08:00
parent 4e7c7c7a50
commit 6450b53084
9 changed files with 2397 additions and 101 deletions
+477 -80
View File
@@ -3,7 +3,7 @@
//| 突破交易策略 - 完整交易版本 |
//+------------------------------------------------------------------+
#property copyright "Breakout Strategy"
#property version "1.01"
#property version "1.03"
#property strict
#include <Trade\Trade.mqh>
@@ -14,6 +14,12 @@ enum ENUM_LOT_SIZE_MODE {
LOT_SIZE_RISK_PERCENT = 1 // 结余风险比例(RiskPercent)
};
// 破高/破低开仓方向
enum ENUM_BREAKOUT_DIRECTION {
BREAKOUT_DIR_FOLLOW = 0, // 顺势(破高多/破低空)
BREAKOUT_DIR_REVERSE = 1 // 反向(破高空/破低多)
};
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
@@ -25,6 +31,11 @@ input double InpMaxWavePercent = 10.0; // 最大波段阈值百分比(%
input double InpPullbackTolerance = 0.0; // 反向突破容忍度(%) 0=不容忍
input int InpMinWaveBars = 3; // 有效波段最少K线数(含两端极值所在K)
input group "=== 突破交易参数 ==="
input ENUM_BREAKOUT_DIRECTION InpBreakoutDirection = BREAKOUT_DIR_FOLLOW; // 开仓方向
input bool InpEnablePullbackReverse = false; // 启用突破回落反向
input int InpPullbackReverseWatchBars = 3; // 回落监测K线数(突破K+后2根=3)
input group "=== 风险管理参数 ==="
input int InpStopLossPoints = 200; // 止损点数
input int InpTakeProfitPoints = 300; // 止盈点数
@@ -60,12 +71,27 @@ struct ValidWaveInfo {
double high_price; // 高点价格
double low_price; // 低点价格
datetime update_time; // 更新时间
bool high_used; // 高点是否已使用
bool low_used; // 低点是否已使用
bool high_used; // 高点-突破挂单已用(每极值限1次)
bool low_used; // 低点-突破挂单已用(每极值限1次)
bool high_pullback_reverse_used; // 高点-回落反向已用(每极值限1次)
bool low_pullback_reverse_used; // 低点-回落反向已用(每极值限1次)
};
struct PullbackWatchState {
bool active;
double extreme_price;
int bars_checked;
};
ValidWaveInfo latest_wave; // 最新有效波段
PullbackWatchState g_high_pullback_watch;
PullbackWatchState g_low_pullback_watch;
datetime g_pullback_bar_time_high = 0;
bool g_pullback_opened_on_bar_high = false;
datetime g_pullback_bar_time_low = 0;
bool g_pullback_opened_on_bar_low = false;
double g_sync_wave_high = 0.0; // 已挂单的波段高价(用于检测换波段)
double g_sync_wave_low = 0.0;
@@ -92,8 +118,17 @@ 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);
void CancelAllEaPendingOrders();
void CancelEaPendingOrderType(const ENUM_ORDER_TYPE order_type);
void CancelEaPendingAtHigh();
void CancelEaPendingAtLow();
ulong FindEaPendingOrder(const ENUM_ORDER_TYPE order_type);
ENUM_ORDER_TYPE PendingTypeOnHighBreakout();
ENUM_ORDER_TYPE PendingTypeOnLowBreakout();
long PosTypeOnHighBreakout();
long PosTypeOnLowBreakout();
void SyncPendingAtExtreme(const bool at_high, const double trigger_price,
const double wave_high, const double wave_low, const double lots);
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);
@@ -105,6 +140,20 @@ double NormalizeVolumeLots(double lots);
void ManagePositions();
void CheckTrailingStop(ulong ticket);
void CheckAndCloseManualOrders();
void CheckPullbackReverseSignals();
void TryArmPullbackWatch();
void ProcessPullbackReverseOnNewBar();
ENUM_ORDER_TYPE OrderTypeOnHighPullbackReverse();
ENUM_ORDER_TYPE OrderTypeOnLowPullbackReverse();
bool IsPullbackBarOpenAllowed(const bool for_high_extreme);
void MarkPullbackBarOpened(const bool for_high_extreme);
void SyncPullbackBarLock(const bool for_high_extreme);
bool IsPullbackOpenBlocked();
bool OpenPullbackReversePosition(ENUM_ORDER_TYPE order_type, const bool from_high_extreme,
const double extreme_price, const double wave_high,
const double wave_low);
bool CalcMarketSlTp(const ENUM_ORDER_TYPE order_type, const double entry_price, double &sl,
double &tp);
//+------------------------------------------------------------------+
//| Expert initialization function |
@@ -130,6 +179,10 @@ int OnInit()
latest_wave.update_time = 0;
latest_wave.high_used = false;
latest_wave.low_used = false;
latest_wave.high_pullback_reverse_used = false;
latest_wave.low_pullback_reverse_used = false;
g_high_pullback_watch.active = false;
g_low_pullback_watch.active = false;
Print("========================================");
Print("突破交易策略EA初始化成功");
@@ -154,7 +207,15 @@ int OnInit()
Print("手数模式: 结余风险比例 ", InpRiskPercent, "% (按止损距离反推手数)");
else
Print("手数模式: 固定手数 ", InpFixedLots);
Print("开仓方式: 突破挂单 (高点BUY STOP / 低点SELL STOP)");
if(InpBreakoutDirection == BREAKOUT_DIR_FOLLOW)
Print("开仓方向: 顺势(破高多/破低空) 挂单: 高BUY STOP / 低SELL STOP");
else
Print("开仓方向: 反向(破高空/破低多) 挂单: 高SELL LIMIT / 低BUY LIMIT");
if(InpEnablePullbackReverse)
Print("回落反向: 开 破极值后监测", InpPullbackReverseWatchBars,
"根K收盘回到极值内开反向单(与突破挂单独立)");
else
Print("回落反向: 关");
Print("禁止手工单:", (InpCloseManualOrders ? "启用 (自动平掉手工单)" : "禁用"));
Print("========================================");
@@ -171,7 +232,7 @@ void OnDeinit(const int reason)
// 删除所有标记
ObjectsDeleteAll(0, "ValidWave_");
CancelEaPendingOrders(true, true);
CancelAllEaPendingOrders();
Print("突破交易策略EA已卸载");
}
@@ -188,12 +249,22 @@ void OnTradeTransaction(const MqlTradeTransaction& trans,
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;
const string deal_comment = HistoryDealGetString(trans.deal, DEAL_COMMENT);
if(StringFind(deal_comment, "-PB") < 0) {
const ENUM_DEAL_TYPE deal_type =
(ENUM_DEAL_TYPE)HistoryDealGetInteger(trans.deal, DEAL_TYPE);
if(deal_type == DEAL_TYPE_BUY) {
if(PosTypeOnHighBreakout() == POSITION_TYPE_BUY)
latest_wave.high_used = true;
else if(PosTypeOnLowBreakout() == POSITION_TYPE_BUY)
latest_wave.low_used = true;
} else if(deal_type == DEAL_TYPE_SELL) {
if(PosTypeOnHighBreakout() == POSITION_TYPE_SELL)
latest_wave.high_used = true;
else if(PosTypeOnLowBreakout() == POSITION_TYPE_SELL)
latest_wave.low_used = true;
}
}
}
}
}
@@ -276,10 +347,13 @@ void OnTick()
// 2. 更新最新有效波段
UpdateLatestValidWave();
// 3. 同步突破挂单(BUY STOP@高 / SELL STOP@低)
// 3. 突破回落反向(破极值后K线收盘回到极值内,与突破挂单独立)
CheckPullbackReverseSignals();
// 4. 同步突破挂单(顺势/反向由 InpBreakoutDirection 决定)
SyncBreakoutPendingOrders();
// 4. 管理已有持仓
// 5. 管理已有持仓
ManagePositions();
}
@@ -552,12 +626,24 @@ void UpdateLatestValidWave()
high != latest_wave.high_price ||
low != latest_wave.low_price) {
const double prev_high = latest_wave.high_price;
const double prev_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(high != prev_high) {
latest_wave.high_used = false;
latest_wave.high_pullback_reverse_used = false;
g_high_pullback_watch.active = false;
}
if(low != prev_low) {
latest_wave.low_used = false;
latest_wave.low_pullback_reverse_used = false;
g_low_pullback_watch.active = false;
}
// 绘制最新有效波段
if(InpShowMarkers) {
@@ -697,23 +783,57 @@ void FilterBreakouts(const int &breakout_bars[], const int &breakout_types[],
//+------------------------------------------------------------------+
//| 突破挂单管理 |
//+------------------------------------------------------------------+
void CancelEaPendingOrders(const bool cancel_buy_stop, const bool cancel_sell_stop)
long PosTypeOnHighBreakout()
{
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(InpBreakoutDirection == BREAKOUT_DIR_REVERSE)
return POSITION_TYPE_SELL;
return POSITION_TYPE_BUY;
}
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);
}
long PosTypeOnLowBreakout()
{
if(InpBreakoutDirection == BREAKOUT_DIR_REVERSE)
return POSITION_TYPE_BUY;
return POSITION_TYPE_SELL;
}
ENUM_ORDER_TYPE PendingTypeOnHighBreakout()
{
if(InpBreakoutDirection == BREAKOUT_DIR_REVERSE)
return ORDER_TYPE_SELL_LIMIT;
return ORDER_TYPE_BUY_STOP;
}
ENUM_ORDER_TYPE PendingTypeOnLowBreakout()
{
if(InpBreakoutDirection == BREAKOUT_DIR_REVERSE)
return ORDER_TYPE_BUY_LIMIT;
return ORDER_TYPE_SELL_STOP;
}
void CancelEaPendingOrderType(const ENUM_ORDER_TYPE order_type)
{
const ulong ticket = FindEaPendingOrder(order_type);
if(ticket > 0)
trade.OrderDelete(ticket);
}
void CancelEaPendingAtHigh()
{
CancelEaPendingOrderType(ORDER_TYPE_BUY_STOP);
CancelEaPendingOrderType(ORDER_TYPE_SELL_LIMIT);
}
void CancelEaPendingAtLow()
{
CancelEaPendingOrderType(ORDER_TYPE_SELL_STOP);
CancelEaPendingOrderType(ORDER_TYPE_BUY_LIMIT);
}
void CancelAllEaPendingOrders()
{
CancelEaPendingAtHigh();
CancelEaPendingAtLow();
}
ulong FindEaPendingOrder(const ENUM_ORDER_TYPE order_type)
@@ -742,7 +862,7 @@ bool CalcPendingSlTp(const ENUM_ORDER_TYPE pending_type, const double trigger_pr
sl = 0.0;
tp = 0.0;
if(pending_type == ORDER_TYPE_BUY_STOP) {
if(pending_type == ORDER_TYPE_BUY_STOP || pending_type == ORDER_TYPE_BUY_LIMIT) {
sl = NormalizeDouble(trigger_price - stop_loss_amount, _Digits);
tp = NormalizeDouble(trigger_price + take_profit_amount, _Digits);
if(stops_level > 0) {
@@ -751,7 +871,7 @@ bool CalcPendingSlTp(const ENUM_ORDER_TYPE pending_type, const double trigger_pr
if(tp - trigger_price < min_stop_distance)
tp = NormalizeDouble(trigger_price + min_stop_distance, _Digits);
}
} else if(pending_type == ORDER_TYPE_SELL_STOP) {
} else if(pending_type == ORDER_TYPE_SELL_STOP || pending_type == ORDER_TYPE_SELL_LIMIT) {
sl = NormalizeDouble(trigger_price + stop_loss_amount, _Digits);
tp = NormalizeDouble(trigger_price - take_profit_amount, _Digits);
if(stops_level > 0) {
@@ -785,6 +905,10 @@ bool PlaceBreakoutPendingOrder(const ENUM_ORDER_TYPE pending_type, const double
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);
else if(pending_type == ORDER_TYPE_BUY_LIMIT)
result = trade.BuyLimit(lots, trigger_price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, comment);
else if(pending_type == ORDER_TYPE_SELL_LIMIT)
result = trade.SellLimit(lots, trigger_price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, comment);
if(!result) {
Print("挂单失败 ", EnumToString(pending_type), " 触发价:", trigger_price,
@@ -799,10 +923,54 @@ bool PlaceBreakoutPendingOrder(const ENUM_ORDER_TYPE pending_type, const double
return true;
}
void SyncPendingAtExtreme(const bool at_high, const double trigger_price,
const double wave_high, const double wave_low,
const double lots)
{
const ENUM_ORDER_TYPE pending_type = at_high ?
PendingTypeOnHighBreakout() : PendingTypeOnLowBreakout();
const ENUM_ORDER_TYPE stale_type = at_high ?
(pending_type == ORDER_TYPE_BUY_STOP ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_BUY_STOP) :
(pending_type == ORDER_TYPE_SELL_STOP ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_SELL_STOP);
CancelEaPendingOrderType(stale_type);
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
const bool price_crossed = at_high ?
(ask >= trigger_price - _Point * 0.5) :
(bid <= trigger_price + _Point * 0.5);
if(price_crossed) {
if(at_high)
CancelEaPendingAtHigh();
else
CancelEaPendingAtLow();
if(InpShowDebugInfo)
Print("价格已越过波段", (at_high ? "高点" : "低点"),
",暂不挂", EnumToString(pending_type),
" ", (at_high ? "Ask:" : "Bid:"),
(at_high ? ask : bid), " 极值:", trigger_price);
return;
}
ulong ticket = FindEaPendingOrder(pending_type);
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 - trigger_price) > _Point * 0.5 ||
MathAbs(order_lots - lots) > 1e-8) {
trade.OrderDelete(ticket);
ticket = 0;
}
}
if(ticket == 0)
PlaceBreakoutPendingOrder(pending_type, trigger_price, wave_high, wave_low);
}
void SyncBreakoutPendingOrders()
{
if(!latest_wave.exists) {
CancelEaPendingOrders(true, true);
CancelAllEaPendingOrders();
g_sync_wave_high = 0.0;
g_sync_wave_low = 0.0;
return;
@@ -811,7 +979,7 @@ void SyncBreakoutPendingOrders()
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);
CancelAllEaPendingOrders();
return;
}
if(freeze_bar_index > 0) {
@@ -824,7 +992,7 @@ void SyncBreakoutPendingOrders()
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);
CancelAllEaPendingOrders();
g_sync_wave_high = latest_wave.high_price;
g_sync_wave_low = latest_wave.low_price;
}
@@ -849,67 +1017,296 @@ void SyncBreakoutPendingOrders()
}
if(total_positions >= InpMaxPositions) {
CancelEaPendingOrders(true, true);
CancelAllEaPendingOrders();
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);
}
const long high_pos_type = PosTypeOnHighBreakout();
const long low_pos_type = PosTypeOnLowBreakout();
const int high_dir_positions = (high_pos_type == POSITION_TYPE_BUY) ?
buy_positions : sell_positions;
const int low_dir_positions = (low_pos_type == POSITION_TYPE_BUY) ?
buy_positions : sell_positions;
if(!latest_wave.high_used &&
(!InpOnePositionPerDirection || high_dir_positions < 1)) {
SyncPendingAtExtreme(true, wave_high, wave_high, wave_low, lots);
} else {
CancelEaPendingOrders(true, false);
CancelEaPendingAtHigh();
}
// 低点 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);
if(!latest_wave.low_used &&
(!InpOnePositionPerDirection || low_dir_positions < 1)) {
SyncPendingAtExtreme(false, wave_low, wave_high, wave_low, lots);
} else {
CancelEaPendingAtLow();
}
}
//+------------------------------------------------------------------+
//| 突破回落反向(破极值后收盘回到极值内开反向单,与突破挂单独立) |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE OrderTypeOnHighPullbackReverse()
{
return ORDER_TYPE_SELL;
}
ENUM_ORDER_TYPE OrderTypeOnLowPullbackReverse()
{
return ORDER_TYPE_BUY;
}
void SyncPullbackBarLock(const bool for_high_extreme)
{
const datetime bar_time = iTime(_Symbol, InpTimeframe, 0);
if(bar_time == 0)
return;
if(for_high_extreme) {
if(bar_time != g_pullback_bar_time_high) {
g_pullback_bar_time_high = bar_time;
g_pullback_opened_on_bar_high = false;
}
} else {
CancelEaPendingOrders(false, true);
if(bar_time != g_pullback_bar_time_low) {
g_pullback_bar_time_low = bar_time;
g_pullback_opened_on_bar_low = false;
}
}
}
bool IsPullbackBarOpenAllowed(const bool for_high_extreme)
{
SyncPullbackBarLock(for_high_extreme);
if(for_high_extreme)
return !g_pullback_opened_on_bar_high;
return !g_pullback_opened_on_bar_low;
}
void MarkPullbackBarOpened(const bool for_high_extreme)
{
SyncPullbackBarLock(for_high_extreme);
if(for_high_extreme)
g_pullback_opened_on_bar_high = true;
else
g_pullback_opened_on_bar_low = true;
}
bool IsPullbackOpenBlocked()
{
if(InpConsecutiveLosses > 0 && InpFreezeBarCount > 0 && freeze_bar_index > 0) {
if(Bars(_Symbol, InpTimeframe) < freeze_bar_index)
return true;
}
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)
return true;
return false;
}
bool IsPullbackDirectionBlocked(const ENUM_ORDER_TYPE order_type)
{
if(!InpOnePositionPerDirection)
return false;
const long pos_type = (order_type == ORDER_TYPE_BUY) ?
POSITION_TYPE_BUY : POSITION_TYPE_SELL;
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;
if(PositionGetInteger(POSITION_TYPE) == pos_type)
return true;
}
return false;
}
bool CalcMarketSlTp(const ENUM_ORDER_TYPE order_type, const double entry_price, double &sl,
double &tp)
{
const ENUM_ORDER_TYPE pending_equiv = (order_type == ORDER_TYPE_BUY) ?
ORDER_TYPE_BUY_STOP : ORDER_TYPE_SELL_STOP;
return CalcPendingSlTp(pending_equiv, entry_price, sl, tp);
}
bool OpenPullbackReversePosition(ENUM_ORDER_TYPE order_type, const bool from_high_extreme,
const double extreme_price, const double wave_high,
const double wave_low)
{
if(!IsPullbackBarOpenAllowed(from_high_extreme))
return false;
if(IsPullbackOpenBlocked()) {
if(InpShowDebugInfo)
Print("【回落反向】开仓跳过 - 冷冻中或已达最大持仓笔数");
return false;
}
if(IsPullbackDirectionBlocked(order_type)) {
if(InpShowDebugInfo)
Print("【回落反向】开仓跳过 - 同向已有持仓");
return false;
}
const double lots = CalculateLotSize(wave_high, wave_low);
if(lots <= 0.0)
return false;
const double entry_price = (order_type == ORDER_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = 0.0, tp = 0.0;
if(!CalcMarketSlTp(order_type, entry_price, sl, tp))
return false;
const int wave_range_points = (int)MathRound(MathAbs(wave_high - wave_low) / _Point);
const string comment = StringFormat("WR%d-PB", wave_range_points);
bool result = false;
if(order_type == ORDER_TYPE_BUY)
result = trade.Buy(lots, _Symbol, entry_price, sl, tp, comment);
else
result = trade.Sell(lots, _Symbol, entry_price, sl, tp, comment);
if(!result) {
Print("【回落反向】开仓失败: ", trade.ResultRetcode(), " - ",
trade.ResultRetcodeDescription());
return false;
}
MarkPullbackBarOpened(from_high_extreme);
Print("【回落反向】开仓成功 ", (order_type == ORDER_TYPE_BUY ? "多" : "空"),
" 手数:", lots, " 极值:", extreme_price, " SL:", sl, " TP:", tp);
return true;
}
void TryArmPullbackWatch()
{
if(!InpEnablePullbackReverse || !latest_wave.exists)
return;
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask > latest_wave.high_price && !latest_wave.high_pullback_reverse_used) {
if(!g_high_pullback_watch.active ||
g_high_pullback_watch.extreme_price != latest_wave.high_price) {
g_high_pullback_watch.active = true;
g_high_pullback_watch.extreme_price = latest_wave.high_price;
g_high_pullback_watch.bars_checked = 0;
if(InpShowDebugInfo)
Print("【回落反向】开始监测破高 极值:", latest_wave.high_price,
" 共", InpPullbackReverseWatchBars, "根K");
}
}
if(bid < latest_wave.low_price && !latest_wave.low_pullback_reverse_used) {
if(!g_low_pullback_watch.active ||
g_low_pullback_watch.extreme_price != latest_wave.low_price) {
g_low_pullback_watch.active = true;
g_low_pullback_watch.extreme_price = latest_wave.low_price;
g_low_pullback_watch.bars_checked = 0;
if(InpShowDebugInfo)
Print("【回落反向】开始监测破低 极值:", latest_wave.low_price,
" 共", InpPullbackReverseWatchBars, "根K");
}
}
}
void ProcessPullbackReverseOnNewBar()
{
if(!InpEnablePullbackReverse || !latest_wave.exists)
return;
const int max_bars = MathMax(1, InpPullbackReverseWatchBars);
const double wave_high = latest_wave.high_price;
const double wave_low = latest_wave.low_price;
if(g_high_pullback_watch.active && !latest_wave.high_pullback_reverse_used) {
const double close1 = iClose(_Symbol, InpTimeframe, 1);
if(close1 > 0.0 && close1 <= g_high_pullback_watch.extreme_price) {
if(OpenPullbackReversePosition(OrderTypeOnHighPullbackReverse(), true,
g_high_pullback_watch.extreme_price,
wave_high, wave_low)) {
latest_wave.high_pullback_reverse_used = true;
}
g_high_pullback_watch.active = false;
} else {
g_high_pullback_watch.bars_checked++;
if(g_high_pullback_watch.bars_checked >= max_bars) {
if(InpShowDebugInfo)
Print("【回落反向】破高监测结束 未回落极值内");
g_high_pullback_watch.active = false;
}
}
}
if(g_low_pullback_watch.active && !latest_wave.low_pullback_reverse_used) {
const double close1 = iClose(_Symbol, InpTimeframe, 1);
if(close1 > 0.0 && close1 >= g_low_pullback_watch.extreme_price) {
if(OpenPullbackReversePosition(OrderTypeOnLowPullbackReverse(), false,
g_low_pullback_watch.extreme_price,
wave_high, wave_low)) {
latest_wave.low_pullback_reverse_used = true;
}
g_low_pullback_watch.active = false;
} else {
g_low_pullback_watch.bars_checked++;
if(g_low_pullback_watch.bars_checked >= max_bars) {
if(InpShowDebugInfo)
Print("【回落反向】破低监测结束 未回升极值内");
g_low_pullback_watch.active = false;
}
}
}
}
void CheckPullbackReverseSignals()
{
if(!InpEnablePullbackReverse)
return;
TryArmPullbackWatch();
static datetime s_last_bar_time = 0;
const datetime bar_time = iTime(_Symbol, InpTimeframe, 0);
if(bar_time == 0 || bar_time == s_last_bar_time)
return;
s_last_bar_time = bar_time;
ProcessPullbackReverseOnNewBar();
}
//+------------------------------------------------------------------+
//| 止损/止盈距离(点数) |
//+------------------------------------------------------------------+
+40 -11
View File
@@ -3,7 +3,7 @@
//| 突破策略 - 单笔突破开仓 |
//+------------------------------------------------------------------+
#property copyright "Breakout Strategy"
#property version "3.18"
#property version "3.20"
#property strict
#include <Trade\Trade.mqh>
@@ -30,7 +30,7 @@ input group "=== 波段识别参数 ==="
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // K线周期
input int InpMAPeriod = 14; // MA周期
input double InpMinWavePercent = 0.1; // 最小波段阈值百分比(%)
input double InpMaxWavePercent = 0.25; // 最大波段阈值百分比(%)
input double InpMaxWavePercent = 1.0; // 最大波段阈值百分比(%)
input double InpPullbackTolerance = 0.05; // 反向突破容忍度(%) 0=不容忍
input int InpMinWaveBars = 3; // 有效波段最少K线数(含两端极值所在K)
@@ -39,17 +39,17 @@ input ENUM_BREAKOUT_DIRECTION InpBreakoutDirection = BREAKOUT_DIR_RANDOM; // 开
input double InpDailyMADevThreshold = 1.0; // 日MA偏离率阈值(%),|偏离|超过才触发反向,0=关
input int InpBatchTakeProfitPoints = 300; // 按批统盈点数(0=关):均价±点数平该批
input double InpStopLossBalancePct = 30.0; // 止损:持仓浮亏达结余比例(%)全部清仓,0=关
input double InpLargeLotMinVolume = 1.0; // 大单独立止盈手数阈值(>=,0=关)
input int InpLargeLotTakeProfitPoints = 500; // 大单最大止盈点数(0=关,不参与统盈)
input bool InpEnablePullbackReverse = true; // 启用突破回落反向
input double InpLargeLotMinVolume = 0.0; // 大单独立止盈手数阈值(>=,0=关)
input int InpLargeLotTakeProfitPoints = 0; // 大单最大止盈点数(0=关,不参与统盈)
input bool InpEnablePullbackReverse = false; // 启用突破回落反向
input int InpPullbackReverseWatchBars = 3; // 回落监测K线数(突破K+后2根=3)
input group "=== 仓位管理参数 ==="
input string InpTierLotsList =
"0.05,0.05,0.05,0.8,0.8,1.1,1,2,2,1,1,1,2,2,1,1,1,5,5"; // 各档手数(逗号分隔,档数=最多开仓笔数)
"0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,5,4,3,2,1,5"; // 各档手数(逗号分隔,档数=最多开仓笔数)
input double InpMaxLots = 99.0; // 同向最大合计手数(0=不限制)
input double InpMinSameDirAddSpacingPercent = 0.0; // 同向最小开仓间距(%) 0=不限制
input ENUM_DUAL_FULL_ACTION InpDualFullAction = DUAL_FULL_CLEAR_ALL; // 双向满档后处理
input ENUM_DUAL_FULL_ACTION InpDualFullAction = DUAL_FULL_NEW_ROUND; // 双向满档后处理
input group "=== 调试选项 ==="
input bool InpShowDebugInfo = false; // 显示调试信息
@@ -131,6 +131,8 @@ int CountAllBreakoutPositions();
double TotalLotsInDirection(const long pos_type);
bool IsDirectionFullActive(const long pos_type);
void ResetBreakoutWaveEntryFlags();
void ResetTradingStateAfterManualClose();
bool IsManualDealCloseReason(const long reason);
void CheckDualSideFullCapacity();
double NormalizeVolume(double lots);
bool InitTierLotsFromInput();
@@ -1173,6 +1175,24 @@ void ResetBreakoutWaveEntryFlags()
g_low_pullback_watch.active = false;
}
bool IsManualDealCloseReason(const long reason)
{
return (reason == DEAL_REASON_CLIENT ||
reason == DEAL_REASON_MOBILE ||
reason == DEAL_REASON_WEB);
}
void ResetTradingStateAfterManualClose()
{
g_trade_round = 1;
g_dual_full_handled = false;
ResetBreakoutWaveEntryFlags();
g_breakout_opened_on_bar_high = false;
g_breakout_opened_on_bar_low = false;
g_pullback_opened_on_bar_high = false;
g_pullback_opened_on_bar_low = false;
}
int CountAllBreakoutPositions()
{
int count = 0;
@@ -1974,7 +1994,7 @@ void CheckAndCloseManualOrders()
}
//+------------------------------------------------------------------+
//| 成交事件(大单被平台TP平仓时重置满档状态) |
//| 成交事件(手动平仓重置状态 / 大单TP重置满档) |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
const MqlTradeRequest& request,
@@ -1990,18 +2010,27 @@ void OnTradeTransaction(const MqlTradeTransaction& trans,
return;
if(HistoryDealGetInteger(trans.deal, DEAL_ENTRY) != DEAL_ENTRY_OUT)
return;
if(HistoryDealGetInteger(trans.deal, DEAL_REASON) != DEAL_REASON_TP)
return;
const string comment = HistoryDealGetString(trans.deal, DEAL_COMMENT);
if(!IsEaBreakoutComment(comment))
return;
const long reason = HistoryDealGetInteger(trans.deal, DEAL_REASON);
const double vol = HistoryDealGetDouble(trans.deal, DEAL_VOLUME);
const int round_id = ParseRoundFromComment(comment);
if(IsManualDealCloseReason(reason)) {
ResetTradingStateAfterManualClose();
Print("【手动平仓】已重置轮次/满档/极值锁 手数:", vol, " 原轮次:", round_id,
" 备注:", comment, " (马丁档按剩余第1轮持仓重计)");
return;
}
if(reason != DEAL_REASON_TP)
return;
if(!IsLargeLotVolume(vol))
return;
const int round_id = ParseRoundFromComment(comment);
if(round_id == g_trade_round)
g_dual_full_handled = false;
+86 -10
View File
@@ -1,57 +1,133 @@
# 突破策略(极值点突破开仓)
MQL5 EA`20260522_Breakout.mq5`v3.18)。
MQL5 EA`20260522_Breakout.mq5`v3.20)。
## 参数默认值(与输入界面一致)
| 分组 | 参数 | 默认 |
|------|------|------|
| 波段 | K线周期 / MA周期 | M1 / 14 |
| 波段 | 最小/最大波段% | 0.1 / **0.25** |
| 波段 | 最小/最大波段% | 0.1 / **1.0** |
| 波段 | 有效波段最少K线 | **3** |
| 波段 | 反向突破容忍度% | **0.05** |
| 交易 | 开仓方向 | **随机**(可选:顺势 / 反向 / 前日收线相对日MA / **日MA偏离率反向** |
| 交易 | 日MA偏离率阈值% | **1.0**(\|偏离\|超过才触发,0=关) |
| 交易 | 按批统盈点数 / 止损% | **300** / **30** |
| 交易 | 大单止盈阈值 / 点数 | **1.0手** / **500点** |
| 交易 | 回落反向 / 监测K线数 | **** / **3** |
| 仓位 | 各档手数(19档) | 见下 |
| 仓位 | 同向最大合计 / 最小间距% / 满档后 | 99 / **0** / **清仓** |
| 交易 | 大单止盈阈值 / 点数 | **0(关)** / **0(关)** |
| 交易 | 回落反向 / 监测K线数 | **** / **3** |
| 仓位 | 各档手数(14档) | 见下 |
| 仓位 | 同向最大合计 / 最小间距% / 满档后 | 99 / **0** / **开新轮(不清仓)** |
| 调试 | 调试 / 极值标记 / Magic / 禁手工单 | false / true / 20260520 / true |
**各档手数:** `0.05,0.05,0.05,0.8,0.8,1.1,1,2,2,1,1,1,2,2,1,1,1,5,5`19 档)
**各档手数:** `0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,5,4,3,2,1,5`14 档)
## 前日收线相对日MA(开仓方向选项)
取**前一交易日**日 K **收盘价**与**日 K 上同周期 MA**(默认 MA14)比较:
| 前日收线位置 | 允许开仓 |
|-------------|----------|
| **在日 MA 下方** | 破**低**极值 → 空;**高**极值出现回落反向 → 空 |
| **在日 MA 上方** | 破**高**极值 → 多;**低**极值出现回落反向 → 多 |
- 偏空时不做「破高」突破单,等高极值**回落反向**或等破低;偏多时不做「破低」突破单,等低极值**回落反向**或等破高。
- 前日收线恰在 MA 上(中性)时,该模式不开仓。
## 日 MA 偏离率反向(开仓方向选项)
**偏离率** = (当前价 日 MA) ÷ 日 MA × 100%(当前价取买卖中间价,日 MA 周期同 `InpMAPeriod`)。
| 偏离率 | 含义 | 允许开仓(反向/均值回归) |
|--------|------|--------------------------|
| **> +阈值%** | 价显著高于日 MA | 破**高** → 空;**高**极值回落反向 → 空 |
| **< −阈值%** | 价显著低于日 MA | 破**低** → 多;**低**极值回落反向 → 多 |
| \|偏离\| ≤ 阈值 | 未超阈值 | **不开仓** |
参数 `日MA偏离率阈值` 默认 **1.0%**,设为 **0** 关闭此逻辑。
## 大单独立止盈
- 开仓手数 **≥ 大单止盈阈值**(默认 1 手)的单子,按 **大单最大止盈点数** 单独止盈,**不参与按批统盈**的加权均价计算。
- 大单止盈平仓后(平台 TP 或 EA 监测触发),**下一档马丁手数**与**统盈均价**均仅按**当前仍持仓**的单子重算,与已平大单无关。
- 阈值或点数设为 **0** 时关闭此功能,所有单子统一走按批统盈。
- 默认**关闭**(阈值 0 或点数 0)。开启后:开仓手数 **≥ 阈值** 的单子按 **点数** 单独止盈,**不参与按批统盈**
- 大单止盈平仓后,马丁档与统盈均价按**剩余持仓**重计。
## 手动平仓 EA 单
在终端/手机/Web 上**手动平掉**本 EA 持仓(Magic 与备注 `[突破…]` 一致)时,会立即重置:
- **交易轮次** → 第 1 轮(仅统计备注为第 1 轮的持仓计档;更高轮次旧仓仍保留,统盈仍按各自轮次批处理)
- **双向满档**已处理标记
- **极值锁**:波段高/低突破与回落反向已用标记、回落监测、同 K 线限 1 笔
EA 程序平仓(统盈、止损、满档清仓、大单止盈等)**不会**触发上述重置。
## 说明
- 突破开仓与回落反向共用档位手数;列表长度 = 同向最多开仓笔数。
- 按批统盈、结余%止损、双向满档逻辑见 EA 输入注释。
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
# 20260526 大波段 + 聚合箱体突破 EA
## 逻辑概要
1. **波段识别(结构)**:MA 突破 → 极值 → 有效波段(0.1%~2%,最少 3 根 K)。`InpPullbackTolerance` 默认 0.05%,仅影响结构识别时是否切断波段。
2. **大波段突破**:最新有效波段振幅 **>0.25% 且 ≤2%** 时,在波段高/低挂突破单。备注 `LW{振幅点数}@{挂单价}`(成交后可对比成交价算滑点)。独立 SL/TP/移动止损/止损再挂。
3. **聚合箱体**:从最新向旧连续纳入振幅 **0.1%0.25%** 的小波段,**1~3** 段成箱(超出 3 段则只保留最近 3 段),在箱沿挂突破单。备注 `BX{振幅点数}@{挂单价}`。独立 SL/TP/移动止损/止损再挂。
两套机制 **同时运行、互不影响**(同一 `InpMagicNumber`,靠订单备注区分)。
4. **交易时段(北京时间 UTC+8**:默认 **关闭**;启用后按 `可交易时段`(如 `15:00-02:00`)才挂突破单,时段外撤销全部 EA 挂单,持仓照常管理。多段用逗号分隔。
## 代码参数对照
`参数设置.md``20260526_box_breakout.mq5` 中 input 默认值已与该文档对齐。
## 已移除(参数文档未定义)
回落反向、连续亏损冷冻、最大持仓笔数、单方向限仓、单箱仅一单、箱体合并包络上限、双 Magic 偏移、调试开关等。
+52
View File
@@ -0,0 +1,52 @@
## 波段识别参数
- K线周期:1min
- MA周期:14
- 最小波段振幅百分比:0.1
- 最大波段阈值百分比:2
- 反向突破容忍度:0.05
- 有效波段最少K线数:3
## 大波段突破机制参数
- 启用大波段突破机制:true
- 开仓方向:顺势
- 最小波段振幅百分比(>该值):0.25
- 最大波段振幅百分比(<=该值):2
- 边界止损出局后是否允许再挂一单:false
- 止损点数:80
- 止盈点数:120
- 使用移动止损:false
## 聚合箱体机制参数
- 启用聚合箱体突破机制:true
- 开仓方向:顺势
- 成箱最小波段振幅百分比(>=该值):0.1
- 成箱最大波段振幅百分比(<=该值):0.25
- 成箱最少有效波段数:1
- 成箱最多有效波段数:3
- 边界止损出局后是否允许再挂一单:false
- 止损点数:80
- 止盈点数:120
- 使用移动止损:false
## 仓位管理模式
- 仓位管理模式:RiskPercent模式
- 固定手数(固定手数模式)0.01
- 每笔风险占结余%(RiskPercent模式): 5
- 单笔最大手数:10
## 其他参数
- 禁止手工单:true
- EA魔术号:20260526
- 显示极值点标记:false
- 显示箱体标记:false
- 显示最近有效波段标记:false
## 交易时段(北京时间)
- 启用时段过滤:false(时段外撤掉本 EA 全部挂单;已有持仓仍管理 SL/TP/移动止损)
- 可交易时段:15:00-02:00(跨日:当天 15:00 至次日 02:00 前;02:00 起不可交易至 15:00