feat: 突破马丁

This commit is contained in:
zhangyangbin
2026-05-22 16:42:15 +08:00
parent df2d4144b2
commit 4e510ca489
3 changed files with 510 additions and 124 deletions
+498 -74
View File
@@ -3,7 +3,7 @@
//| 突破策略 - 单笔突破开仓 |
//+------------------------------------------------------------------+
#property copyright "Breakout Strategy"
#property version "3.07"
#property version "3.14"
#property strict
#include <Trade\Trade.mqh>
@@ -28,20 +28,21 @@ input group "=== 波段识别参数 ==="
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // K线周期
input int InpMAPeriod = 14; // MA周期
input double InpMinWavePercent = 0.1; // 最小波段阈值百分比(%)
input double InpMaxWavePercent = 1.0; // 最大波段阈值百分比(%)
input double InpMaxWavePercent = 0.25; // 最大波段阈值百分比(%)
input double InpPullbackTolerance = 0.05; // 反向突破容忍度(%) 0=不容忍
input group "=== 突破交易参数 ==="
input ENUM_BREAKOUT_DIRECTION InpBreakoutDirection = BREAKOUT_DIR_FOLLOW; // 开仓方向
input double InpTakeProfitBalancePct = 1.0; // 止盈:持仓浮盈达结余比例(%)全部清仓,0=关
input ENUM_BREAKOUT_DIRECTION InpBreakoutDirection = BREAKOUT_DIR_RANDOM; // 开仓方向
input int InpBatchTakeProfitPoints = 300; // 按批统盈点数(0=关):均价±点数平该批
input double InpStopLossBalancePct = 30.0; // 止损:持仓浮亏达结余比例(%)全部清仓,0=关
input bool InpEnablePullbackReverse = true; // 启用突破回落反向
input int InpPullbackReverseWatchBars = 3; // 回落监测K线数(突破K+后2根=3)
input group "=== 仓位管理参数 ==="
input double InpFixedLots = 0.5; // 基准手数(同向首笔/每组首笔)
input double InpMartingaleAddLotBoost = 2.0; // 同向加码:每组+手数(0=始终基准)
input int InpMartingaleTierInterval = 5; // 同向加码:档间隔N(每N笔为一组)
input double InpMaxLots = 30.0; // 同向最大合计手数
input double InpMinSameDirAddSpacingPercent = 0.1; // 同向最小开仓间距(%) 0=不限制
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"; // 各档手数(逗号分隔,档数=最多开仓笔数)
input double InpMaxLots = 99.0; // 同向最大合计手数(0=不限制)
input double InpMinSameDirAddSpacingPercent = 0.0; // 同向最小开仓间距(%) 0=不限制
input ENUM_DUAL_FULL_ACTION InpDualFullAction = DUAL_FULL_CLEAR_ALL; // 双向满档后处理
input group "=== 调试选项 ==="
@@ -64,6 +65,14 @@ struct ValidWaveInfo {
datetime update_time; // 更新时间
bool high_breakout_used; // 高点-突破开仓已用(每极值限1次)
bool low_breakout_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; // 最新有效波段
@@ -76,6 +85,16 @@ bool g_breakout_opened_on_bar_low = false;
int g_trade_round = 1; // 当前交易轮次(新轮仅统计该轮持仓)
bool g_dual_full_handled = false; // 本轮双向满档是否已处理
double g_tier_lots[];
int g_tier_lots_count = 0;
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;
// 极值点结构体定义
struct ExtremePoint {
datetime time;
@@ -102,17 +121,34 @@ bool IsDirectionFullActive(const long pos_type);
void ResetBreakoutWaveEntryFlags();
void CheckDualSideFullCapacity();
double NormalizeVolume(double lots);
double LotForSameDirectionTier(const int tier_index, const double base_lot);
bool InitTierLotsFromInput();
int GetMaxTierSlots();
double LotForTierIndex(const int tier_index);
double CalculateLotSize(const long pos_type);
bool IsSameDirectionTierSlotsFull(const long pos_type);
long PosTypeOnHighBreakout();
long PosTypeOnLowBreakout();
void CheckBreakoutSignals();
void CheckPullbackReverseSignals();
void TryArmPullbackWatch();
void ProcessPullbackReverseOnNewBar();
ENUM_ORDER_TYPE OrderTypeOnHighPullbackReverse();
ENUM_ORDER_TYPE OrderTypeOnLowPullbackReverse();
string FormatPullbackReverseComment(const int round);
bool IsPullbackBarOpenAllowed(const bool for_high_extreme);
void MarkPullbackBarOpened(const bool for_high_extreme);
bool OpenPullbackReversePosition(ENUM_ORDER_TYPE order_type, const bool from_high_extreme,
const double extreme_price);
ENUM_ORDER_TYPE OrderTypeOnHighBreakout();
ENUM_ORDER_TYPE OrderTypeOnLowBreakout();
void CheckAndCloseManualOrders();
double TotalBreakoutFloatingPL();
bool CloseAllBreakoutPositions(const string reason);
void CheckBreakoutBalanceExit();
void CollectAllTradeRounds(int &rounds[], int &n_rounds);
bool BatchRoundAvgPrice(const int round_id, const long pos_type, double &avg, double &sum_lots);
bool CloseBreakoutRoundBatch(const int round_id, const long pos_type, const string reason);
void CheckBreakoutBatchTakeProfit();
void CheckBreakoutBalanceStopLoss();
bool OpenBreakoutPosition(ENUM_ORDER_TYPE order_type, double wave_high, double wave_low,
const bool from_high_extreme);
void SyncBreakoutBarLock(const bool for_high_extreme);
@@ -145,12 +181,21 @@ int OnInit()
latest_wave.update_time = 0;
latest_wave.high_breakout_used = false;
latest_wave.low_breakout_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;
MathSrand((int)(TimeLocal() ^ GetTickCount()));
g_trade_round = 1;
g_dual_full_handled = false;
if(!InitTierLotsFromInput()) {
Print("解析各档手数失败,请检查 InpTierLotsList");
return(INIT_FAILED);
}
Print("========================================");
Print("突破EA初始化成功 (极值点突破开仓)");
Print("品种:", _Symbol);
@@ -168,10 +213,19 @@ int OnInit()
else
Print("开仓方向: 随机(每次突破独立随机)");
Print("开仓限制: 每个极值点(破高/破低)各最多1笔,可多笔并存");
Print("止盈(结余%):", InpTakeProfitBalancePct, " (0=关闭,达标全部清仓)");
Print("按批统盈点数:", InpBatchTakeProfitPoints, " (0=关闭,均价±点数平该轮该向)");
Print("止损(结余%):", InpStopLossBalancePct, " (0=关闭,达标全部清仓)");
Print("同向手数: 基准=", InpFixedLots, " 档间隔=", InpMartingaleTierInterval,
" 每组+", InpMartingaleAddLotBoost, " 同向上限=", InpMaxLots);
if(InpEnablePullbackReverse)
Print("回落反向: 开 破极值后监测", InpPullbackReverseWatchBars,
"根K收盘回到极值内开反向单(与突破开仓独立)");
else
Print("回落反向: 关");
Print("同向各档手数: 共", g_tier_lots_count, "档(第1档=", g_tier_lots[0],
" 末档=", g_tier_lots[g_tier_lots_count - 1], ")");
if(InpMaxLots > 0.0)
Print("同向合计手数上限:", InpMaxLots);
else
Print("同向合计手数上限: 不限制");
if(InpMinSameDirAddSpacingPercent > 0.0)
Print("同向最小间距%:", InpMinSameDirAddSpacingPercent,
" (多:现价须≤上一笔×(1-%); 空:现价须≥上一笔×(1+%))");
@@ -213,13 +267,17 @@ void OnTick()
// 2. 更新最新有效波段
UpdateLatestValidWave();
// 3. 结余比例止盈/止损(全部持仓,含历史轮)
CheckBreakoutBalanceExit();
// 3. 按批统盈(均价+点数) / 结余比例止损(全部持仓)
CheckBreakoutBatchTakeProfit();
CheckBreakoutBalanceStopLoss();
// 4. 当前轮双向同向均满档 → 清仓或开新轮
CheckDualSideFullCapacity();
// 5. 突破信号(破极值开仓,每极值点各限1笔)
// 5. 突破回落反向(破极值后K线收盘回到极值内)
CheckPullbackReverseSignals();
// 6. 突破信号(破极值开仓,每极值点各限1笔,与回落反向独立)
CheckBreakoutSignals();
}
@@ -479,10 +537,16 @@ void UpdateLatestValidWave()
latest_wave.update_time = extremes[i].time;
// 极值价格更新 → 该侧可再开1笔(与是否已有其它持仓无关)
if(high != prev_high)
if(high != prev_high) {
latest_wave.high_breakout_used = false;
if(low != prev_low)
latest_wave.high_pullback_reverse_used = false;
g_high_pullback_watch.active = false;
}
if(low != prev_low) {
latest_wave.low_breakout_used = false;
latest_wave.low_pullback_reverse_used = false;
g_low_pullback_watch.active = false;
}
// 绘制最新有效波段
if(InpShowMarkers) {
@@ -625,22 +689,22 @@ ENUM_ORDER_TYPE RandomBreakoutOrderType()
int ParseRoundFromComment(const string &comment)
{
const string prefix = "[突破-R";
const int i = StringFind(comment, prefix);
if(i == 0) {
const int start = i + StringLen(prefix);
const int end_br = StringFind(comment, "]", start);
if(end_br > start)
return (int)StringToInteger(StringSubstr(comment, start, end_br - start));
}
if(StringFind(comment, "[突破]") == 0)
if(StringFind(comment, "[突破") != 0)
return 0;
const int i_r = StringFind(comment, "-R");
if(i_r < 0)
return 1;
return 0;
const int start = i_r + 2;
const int end_br = StringFind(comment, "]", start);
if(end_br <= start)
return 1;
const int r = (int)StringToInteger(StringSubstr(comment, start, end_br - start));
return (r >= 1) ? r : 1;
}
bool IsEaBreakoutComment(const string &comment)
{
return (ParseRoundFromComment(comment) > 0);
return (StringFind(comment, "[突破") == 0);
}
string FormatBreakoutComment(const int round)
@@ -650,10 +714,202 @@ string FormatBreakoutComment(const int round)
return StringFormat("[突破-R%d]", round);
}
string FormatPullbackReverseComment(const int round)
{
if(round <= 1)
return "[突破-回落]";
return StringFormat("[突破-回落-R%d]", round);
}
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 {
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 OpenPullbackReversePosition(ENUM_ORDER_TYPE order_type, const bool from_high_extreme,
const double extreme_price)
{
if(!IsPullbackBarOpenAllowed(from_high_extreme))
return false;
const long pos_type = (order_type == ORDER_TYPE_BUY) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
const double lots = CalculateLotSize(pos_type);
if(lots <= 0.0)
return false;
if(InpMaxLots > 0.0) {
const double cur_lots = TotalLotsInDirection(pos_type);
if(cur_lots + lots > InpMaxLots + 1e-8) {
if(InpShowDebugInfo)
Print("【回落反向】开仓跳过 - 同向合计将超上限 ", InpMaxLots);
return false;
}
}
if(!IsSameDirMinSpacingSatisfied(pos_type)) {
if(InpShowDebugInfo)
Print("【回落反向】开仓跳过 - 未达同向最小间距%");
return false;
}
const string comment = FormatPullbackReverseComment(g_trade_round);
bool result = false;
if(order_type == ORDER_TYPE_BUY)
result = trade.Buy(lots, _Symbol, 0, 0, 0, comment);
else
result = trade.Sell(lots, _Symbol, 0, 0, 0, comment);
if(!result) {
Print("【回落反向】开仓失败: ", trade.ResultRetcode(), " - ",
trade.ResultRetcodeDescription());
return false;
}
MarkPullbackBarOpened(from_high_extreme);
Print("【回落反向】开仓成功 ", (order_type == ORDER_TYPE_BUY ? "多" : "空"),
" 手数:", lots, " 极值:", extreme_price, " 备注:", comment);
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);
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))
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))
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();
}
void ResetBreakoutWaveEntryFlags()
{
latest_wave.high_breakout_used = false;
latest_wave.low_breakout_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;
}
int CountAllBreakoutPositions()
@@ -715,16 +971,27 @@ double TotalLotsInDirection(const long pos_type)
return sum;
}
bool IsSameDirectionTierSlotsFull(const long pos_type)
{
const int tier = CountBreakoutPositions(pos_type);
if(tier >= g_tier_lots_count)
return true;
const double next_lot = LotForTierIndex(tier);
if(next_lot <= 0.0)
return true;
if(InpMaxLots > 0.0) {
const double cur = TotalLotsInDirection(pos_type);
if(cur >= InpMaxLots - 1e-8)
return true;
if(cur + next_lot > InpMaxLots + 1e-8)
return true;
}
return false;
}
bool IsDirectionFullActive(const long pos_type)
{
const double cur = TotalLotsInDirection(pos_type);
if(cur >= InpMaxLots - 1e-8)
return true;
// 按下一笔实际加码手数判断(非仅基准手): 如19.5手+下一笔4.5>20亦视为满档
const double next_lot = CalculateLotSize(pos_type);
if(next_lot <= 0.0)
return (cur > 0.0);
return (cur + next_lot > InpMaxLots + 1e-8);
return IsSameDirectionTierSlotsFull(pos_type);
}
void CheckDualSideFullCapacity()
@@ -842,26 +1109,53 @@ double NormalizeVolume(double lots)
return lots;
}
// tier_index: 0=该方向首笔, 1=第2笔…;grp=tier/N,组内手数相同,每组=基准+grp×加码
double LotForSameDirectionTier(const int tier_index, const double base_lot)
bool InitTierLotsFromInput()
{
if(tier_index < 0 || base_lot <= 0.0)
ArrayResize(g_tier_lots, 0);
g_tier_lots_count = 0;
string parts[];
const int n = StringSplit(InpTierLotsList, ',', parts);
if(n <= 0)
return false;
for(int i = 0; i < n; i++) {
string token = parts[i];
StringTrimLeft(token);
StringTrimRight(token);
if(StringLen(token) == 0)
continue;
const double v = StringToDouble(token);
if(v <= 0.0)
continue;
ArrayResize(g_tier_lots, g_tier_lots_count + 1);
g_tier_lots[g_tier_lots_count++] = v;
}
return (g_tier_lots_count > 0);
}
int GetMaxTierSlots()
{
return g_tier_lots_count;
}
double LotForTierIndex(const int tier_index)
{
if(tier_index < 0 || tier_index >= g_tier_lots_count)
return 0.0;
const int n = MathMax(1, InpMartingaleTierInterval);
const int grp = tier_index / n;
if(InpMartingaleAddLotBoost <= 0.0)
return NormalizeVolume(base_lot);
double vol = base_lot + (double)grp * InpMartingaleAddLotBoost;
return NormalizeVolume(MathMin(vol, InpMaxLots));
double vol = g_tier_lots[tier_index];
if(InpMaxLots > 0.0)
vol = MathMin(vol, InpMaxLots);
return NormalizeVolume(vol);
}
//+------------------------------------------------------------------+
//| 按同向持仓笔数(档)计算本笔手数 |
//| 按同向已有笔数取下一档手数(tier=0为第1笔) |
//+------------------------------------------------------------------+
double CalculateLotSize(const long pos_type)
{
const int tier = CountBreakoutPositions(pos_type);
return LotForSameDirectionTier(tier, InpFixedLots);
return LotForTierIndex(tier);
}
bool GetLastSameDirectionOpenPrice(const long pos_type, double &last_open)
@@ -964,8 +1258,146 @@ bool CloseAllBreakoutPositions(const string reason)
return ok && n > 0;
}
void CheckBreakoutBalanceExit()
void CollectAllTradeRounds(int &rounds[], int &n_rounds)
{
n_rounds = 0;
ArrayResize(rounds, 0);
const int total = PositionsTotal();
for(int i = 0; i < total; i++) {
if(!PositionSelectByTicket(PositionGetTicket(i)))
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
const int r = ParseRoundFromComment(PositionGetString(POSITION_COMMENT));
if(r < 1)
continue;
bool found = false;
for(int j = 0; j < n_rounds; j++) {
if(rounds[j] == r) {
found = true;
break;
}
}
if(!found) {
ArrayResize(rounds, n_rounds + 1);
rounds[n_rounds++] = r;
}
}
}
bool BatchRoundAvgPrice(const int round_id, const long pos_type, double &avg, double &sum_lots)
{
avg = 0.0;
sum_lots = 0.0;
double sum_px_vol = 0.0;
const int total = PositionsTotal();
for(int i = 0; i < total; i++) {
if(!PositionSelectByTicket(PositionGetTicket(i)))
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
if(PositionGetInteger(POSITION_TYPE) != pos_type)
continue;
if(ParseRoundFromComment(PositionGetString(POSITION_COMMENT)) != round_id)
continue;
const double v = PositionGetDouble(POSITION_VOLUME);
const double p = PositionGetDouble(POSITION_PRICE_OPEN);
sum_px_vol += p * v;
sum_lots += v;
}
if(sum_lots <= 1e-12)
return false;
avg = sum_px_vol / sum_lots;
return true;
}
bool CloseBreakoutRoundBatch(const int round_id, const long pos_type, const string reason)
{
ulong tickets[];
int n = 0;
const int total = PositionsTotal();
for(int i = 0; i < total; i++) {
if(!PositionSelectByTicket(PositionGetTicket(i)))
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
if(PositionGetInteger(POSITION_TYPE) != pos_type)
continue;
if(ParseRoundFromComment(PositionGetString(POSITION_COMMENT)) != round_id)
continue;
ArrayResize(tickets, n + 1);
tickets[n++] = PositionGetTicket(i);
}
if(n <= 0)
return false;
bool ok = true;
for(int j = 0; j < n; j++) {
if(!trade.PositionClose(tickets[j]))
ok = false;
}
if(ok) {
Print("【突破】", (pos_type == POSITION_TYPE_BUY ? "多单" : "空单"),
" 第", round_id, "轮批平仓 笔数=", n, " 原因:", reason);
if(round_id == g_trade_round && CountBreakoutPositions(pos_type) == 0)
g_dual_full_handled = false;
}
return ok;
}
void CheckBreakoutBatchTakeProfitForRoundDir(const int round_id, const long pos_type)
{
double avg = 0.0, sum_lots = 0.0;
if(!BatchRoundAvgPrice(round_id, pos_type, avg, sum_lots))
return;
const double tp_off = (double)InpBatchTakeProfitPoints * _Point;
bool hit = false;
if(pos_type == POSITION_TYPE_BUY) {
const double px = SymbolInfoDouble(_Symbol, SYMBOL_BID);
hit = (px >= avg + tp_off);
} else {
const double px = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
hit = (px <= avg - tp_off);
}
if(!hit)
return;
const double target = (pos_type == POSITION_TYPE_BUY) ? (avg + tp_off) : (avg - tp_off);
CloseBreakoutRoundBatch(round_id, pos_type,
StringFormat("统盈 均价%s+%d点 目标%s",
DoubleToString(avg, _Digits), InpBatchTakeProfitPoints,
DoubleToString(target, _Digits)));
}
void CheckBreakoutBatchTakeProfit()
{
if(InpBatchTakeProfitPoints <= 0)
return;
int rounds[];
int n_rounds = 0;
CollectAllTradeRounds(rounds, n_rounds);
for(int i = 0; i < n_rounds; i++) {
CheckBreakoutBatchTakeProfitForRoundDir(rounds[i], POSITION_TYPE_BUY);
CheckBreakoutBatchTakeProfitForRoundDir(rounds[i], POSITION_TYPE_SELL);
}
}
void CheckBreakoutBalanceStopLoss()
{
if(InpStopLossBalancePct <= 0.0)
return;
if(CountAllBreakoutPositions() == 0)
return;
@@ -974,23 +1406,10 @@ void CheckBreakoutBalanceExit()
return;
const double fpl = TotalBreakoutFloatingPL();
if(InpTakeProfitBalancePct > 0.0) {
const double tp_need = balance * (InpTakeProfitBalancePct / 100.0);
if(fpl >= tp_need) {
CloseAllBreakoutPositions(
StringFormat("止盈:浮盈%.2f达结余%.2f%%", fpl, InpTakeProfitBalancePct));
return;
}
}
if(InpStopLossBalancePct > 0.0) {
const double sl_need = balance * (InpStopLossBalancePct / 100.0);
if(fpl <= -sl_need) {
CloseAllBreakoutPositions(
StringFormat("止损:浮亏%.2f达结余%.2f%%", fpl, InpStopLossBalancePct));
}
}
const double sl_need = balance * (InpStopLossBalancePct / 100.0);
if(fpl <= -sl_need)
CloseAllBreakoutPositions(
StringFormat("止损:浮亏%.2f达结余%.2f%%", fpl, InpStopLossBalancePct));
}
void SyncBreakoutBarLock(const bool for_high_extreme)
@@ -1040,12 +1459,18 @@ bool OpenBreakoutPosition(ENUM_ORDER_TYPE order_type, double wave_high, double w
if(lots <= 0.0)
return false;
const double cur_lots = TotalLotsInDirection(pos_type);
if(cur_lots + lots > InpMaxLots + 1e-8) {
Print("【突破】开仓跳过 - 同向合计将超上限 ", InpMaxLots,
" 当前:", cur_lots, " 拟开:", lots);
if(lots <= 0.0) {
Print("【突破】开仓跳过 - 同向已达最大档数 ", g_tier_lots_count);
return false;
}
if(InpMaxLots > 0.0) {
const double cur_lots = TotalLotsInDirection(pos_type);
if(cur_lots + lots > InpMaxLots + 1e-8) {
Print("【突破】开仓跳过 - 同向合计将超上限 ", InpMaxLots,
" 当前:", cur_lots, " 拟开:", lots);
return false;
}
}
if(!IsSameDirMinSpacingSatisfied(pos_type)) {
if(InpShowDebugInfo) {
@@ -1072,10 +1497,9 @@ bool OpenBreakoutPosition(ENUM_ORDER_TYPE order_type, double wave_high, double w
}
MarkBreakoutBarOpened(from_high_extreme);
const int tier = CountBreakoutPositions(pos_type) - 1;
const int grp = (tier >= 0) ? (tier / MathMax(1, InpMartingaleTierInterval)) : 0;
const int tier = CountBreakoutPositions(pos_type);
Print("【突破】开仓成功 ", (order_type == ORDER_TYPE_BUY ? "多" : "空"),
" 手数:", lots, " 轮次:", g_trade_round, " 同向第", (tier + 1), "笔/组", (grp + 1),
" 手数:", lots, " 轮次:", g_trade_round, " 同向第", tier, "/", g_tier_lots_count, "档",
" 备注:", comment, " 波段 H:", wave_high, " L:", wave_low);
return true;
}
+12 -50
View File
@@ -1,62 +1,24 @@
# 突破策略(极值点突破开仓)
MQL5 EA`20260520_Breakout.mq5`v3.07)。
MQL5 EA`20260520_Breakout.mq5`v3.14)。
## 参数默认值(与输入界面一致)
| 分组 | 参数 | 默认 |
|------|------|------|
| 波段 | K线周期 / MA周期 | M1 / 14 |
| 波段 | 最小/最大波段% | 0.1 / **1.0** |
| 波段 | 最小/最大波段% | 0.1 / **0.25** |
| 波段 | 反向突破容忍度% | **0.05** |
| 交易 | 开仓方向 | **顺势**(破高多/破低空) |
| 交易 | 止盈:浮盈达结余比例(%) | **1**0=关) |
| 交易 | 止损:浮亏达结余比例(%) | **30**0=关) |
| 仓位 | 基准手数 / 每组+ / 档间隔N / 同向上限 / 同向最小间距% | **0.5** / **2** / 5 / **30** / **0.1** |
| 仓位 | 双向满档后处理 | **清仓**(平掉全部 EA 持仓) |
| 调试 | 调试信息 / 极值标记 / Magic / 禁手工单 | false / true / 20260520 / true |
| 交易 | 开仓方向 | **随机** |
| 交易 | 按批统盈点数 / 止损% | **300** / **30** |
| 交易 | 回落反向 / 监测K线数 | **开** / **3** |
| 仓位 | 各档手数(19档) | 见下 |
| 仓位 | 同向最大合计 / 最小间距% / 满档后 | 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 档)
- 识别**最新有效波段**后,价格**破高**或**破低**即尝试开仓。
- **破高、破低各算一个极值点,每个极值点最多对应 1 笔**(当前轮内)。
- 可同时持有多笔;**仅统计当前轮**的手数、笔数、间距、满档。
- 第 1 轮备注 `[突破]`,第 2 轮起 `[突破-R2]``[突破-R3]`
## 说明
### 同向加码手数(类马丁)
- 按**当前轮、本方向已有笔数**定档。
- `组号 = tier ÷ 档间隔N`**本笔手数** = `基准 + 组号 × 每组+`
- **当前轮**该方向合计不得超过 `同向最大合计手数`
### 同向最小开仓间距
- **0 = 不限制**;仅相对**当前轮**最近一笔同向开仓价判断。
### 开仓方向
| 选项 | 破高 | 破低 |
|------|------|------|
| **顺势** | 多 | 空 |
| **反向** | 空 | 多 |
| **随机** | 每次突破独立随机 | |
## 双向满档(当前轮多空均达同向上限)
当**当前轮**多单、空单均**无法再开下一笔**(合计已达上限,或「当前合计 + 下一档手数」会超限,例如 19.5+4.5>20)时触发一次:
| 模式 | 行为 |
|------|------|
| **清仓**(默认) | 平掉本 EA 全部持仓(各轮一并平掉),轮次重置为 1,极值标记重置 |
| **不处理旧仓,开始下一轮** | 旧仓保留;轮次 +1;新单从基准手数重新加码 |
触发后需等当前轮任一侧未满档,才会在再次双向满档时重复处理。
## 平仓(结余比例)
- 统计**全部轮次**持仓浮盈浮亏(含 swap)。
- 止盈 / 止损达标 → **全部清仓**(各轮一并平掉)。
## 手工单
- `禁止手工单=true` 时自动平掉本品种 Magic=0 的手工单。
- 突破开仓与回落反向共用档位手数;列表长度 = 同向最多开仓笔数。
- 按批统盈、结余%止损、双向满档逻辑见 EA 输入注释。
Binary file not shown.