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 copyright "Breakout Strategy"
#property version "3.07" #property version "3.14"
#property strict #property strict
#include <Trade\Trade.mqh> #include <Trade\Trade.mqh>
@@ -28,20 +28,21 @@ input group "=== 波段识别参数 ==="
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // K线周期 input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // K线周期
input int InpMAPeriod = 14; // MA周期 input int InpMAPeriod = 14; // MA周期
input double InpMinWavePercent = 0.1; // 最小波段阈值百分比(%) input double InpMinWavePercent = 0.1; // 最小波段阈值百分比(%)
input double InpMaxWavePercent = 1.0; // 最大波段阈值百分比(%) input double InpMaxWavePercent = 0.25; // 最大波段阈值百分比(%)
input double InpPullbackTolerance = 0.05; // 反向突破容忍度(%) 0=不容忍 input double InpPullbackTolerance = 0.05; // 反向突破容忍度(%) 0=不容忍
input group "=== 突破交易参数 ===" input group "=== 突破交易参数 ==="
input ENUM_BREAKOUT_DIRECTION InpBreakoutDirection = BREAKOUT_DIR_FOLLOW; // 开仓方向 input ENUM_BREAKOUT_DIRECTION InpBreakoutDirection = BREAKOUT_DIR_RANDOM; // 开仓方向
input double InpTakeProfitBalancePct = 1.0; // 止盈:持仓浮盈达结余比例(%)全部清仓,0=关 input int InpBatchTakeProfitPoints = 300; // 按批统盈点数(0=关):均价±点数平该批
input double InpStopLossBalancePct = 30.0; // 止损:持仓浮亏达结余比例(%)全部清仓,0=关 input double InpStopLossBalancePct = 30.0; // 止损:持仓浮亏达结余比例(%)全部清仓,0=关
input bool InpEnablePullbackReverse = true; // 启用突破回落反向
input int InpPullbackReverseWatchBars = 3; // 回落监测K线数(突破K+后2根=3)
input group "=== 仓位管理参数 ===" input group "=== 仓位管理参数 ==="
input double InpFixedLots = 0.5; // 基准手数(同向首笔/每组首笔) input string InpTierLotsList =
input double InpMartingaleAddLotBoost = 2.0; // 同向加码:每组+手数(0=始终基准) "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 int InpMartingaleTierInterval = 5; // 同向加码:档间隔N(每N笔为一组) input double InpMaxLots = 99.0; // 同向最大合计手数(0=不限制)
input double InpMaxLots = 30.0; // 同向最大合计手数 input double InpMinSameDirAddSpacingPercent = 0.0; // 同向最小开仓间距(%) 0=不限制
input double InpMinSameDirAddSpacingPercent = 0.1; // 同向最小开仓间距(%) 0=不限制
input ENUM_DUAL_FULL_ACTION InpDualFullAction = DUAL_FULL_CLEAR_ALL; // 双向满档后处理 input ENUM_DUAL_FULL_ACTION InpDualFullAction = DUAL_FULL_CLEAR_ALL; // 双向满档后处理
input group "=== 调试选项 ===" input group "=== 调试选项 ==="
@@ -64,6 +65,14 @@ struct ValidWaveInfo {
datetime update_time; // 更新时间 datetime update_time; // 更新时间
bool high_breakout_used; // 高点-突破开仓已用(每极值限1次) bool high_breakout_used; // 高点-突破开仓已用(每极值限1次)
bool low_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; // 最新有效波段 ValidWaveInfo latest_wave; // 最新有效波段
@@ -76,6 +85,16 @@ bool g_breakout_opened_on_bar_low = false;
int g_trade_round = 1; // 当前交易轮次(新轮仅统计该轮持仓) int g_trade_round = 1; // 当前交易轮次(新轮仅统计该轮持仓)
bool g_dual_full_handled = false; // 本轮双向满档是否已处理 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 { struct ExtremePoint {
datetime time; datetime time;
@@ -102,17 +121,34 @@ bool IsDirectionFullActive(const long pos_type);
void ResetBreakoutWaveEntryFlags(); void ResetBreakoutWaveEntryFlags();
void CheckDualSideFullCapacity(); void CheckDualSideFullCapacity();
double NormalizeVolume(double lots); 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); double CalculateLotSize(const long pos_type);
bool IsSameDirectionTierSlotsFull(const long pos_type);
long PosTypeOnHighBreakout(); long PosTypeOnHighBreakout();
long PosTypeOnLowBreakout(); long PosTypeOnLowBreakout();
void CheckBreakoutSignals(); 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 OrderTypeOnHighBreakout();
ENUM_ORDER_TYPE OrderTypeOnLowBreakout(); ENUM_ORDER_TYPE OrderTypeOnLowBreakout();
void CheckAndCloseManualOrders(); void CheckAndCloseManualOrders();
double TotalBreakoutFloatingPL(); double TotalBreakoutFloatingPL();
bool CloseAllBreakoutPositions(const string reason); 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, bool OpenBreakoutPosition(ENUM_ORDER_TYPE order_type, double wave_high, double wave_low,
const bool from_high_extreme); const bool from_high_extreme);
void SyncBreakoutBarLock(const bool for_high_extreme); void SyncBreakoutBarLock(const bool for_high_extreme);
@@ -145,12 +181,21 @@ int OnInit()
latest_wave.update_time = 0; latest_wave.update_time = 0;
latest_wave.high_breakout_used = false; latest_wave.high_breakout_used = false;
latest_wave.low_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())); MathSrand((int)(TimeLocal() ^ GetTickCount()));
g_trade_round = 1; g_trade_round = 1;
g_dual_full_handled = false; g_dual_full_handled = false;
if(!InitTierLotsFromInput()) {
Print("解析各档手数失败,请检查 InpTierLotsList");
return(INIT_FAILED);
}
Print("========================================"); Print("========================================");
Print("突破EA初始化成功 (极值点突破开仓)"); Print("突破EA初始化成功 (极值点突破开仓)");
Print("品种:", _Symbol); Print("品种:", _Symbol);
@@ -168,10 +213,19 @@ int OnInit()
else else
Print("开仓方向: 随机(每次突破独立随机)"); Print("开仓方向: 随机(每次突破独立随机)");
Print("开仓限制: 每个极值点(破高/破低)各最多1笔,可多笔并存"); Print("开仓限制: 每个极值点(破高/破低)各最多1笔,可多笔并存");
Print("止盈(结余%):", InpTakeProfitBalancePct, " (0=关闭,达标全部清仓)"); Print("按批统盈点数:", InpBatchTakeProfitPoints, " (0=关闭,均价±点数平该轮该向)");
Print("止损(结余%):", InpStopLossBalancePct, " (0=关闭,达标全部清仓)"); Print("止损(结余%):", InpStopLossBalancePct, " (0=关闭,达标全部清仓)");
Print("同向手数: 基准=", InpFixedLots, " 档间隔=", InpMartingaleTierInterval, if(InpEnablePullbackReverse)
" 每组+", InpMartingaleAddLotBoost, " 同向上限=", InpMaxLots); 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) if(InpMinSameDirAddSpacingPercent > 0.0)
Print("同向最小间距%:", InpMinSameDirAddSpacingPercent, Print("同向最小间距%:", InpMinSameDirAddSpacingPercent,
" (多:现价须≤上一笔×(1-%); 空:现价须≥上一笔×(1+%))"); " (多:现价须≤上一笔×(1-%); 空:现价须≥上一笔×(1+%))");
@@ -213,13 +267,17 @@ void OnTick()
// 2. 更新最新有效波段 // 2. 更新最新有效波段
UpdateLatestValidWave(); UpdateLatestValidWave();
// 3. 结余比例止盈/止损(全部持仓,含历史轮) // 3. 按批统盈(均价+点数) / 结余比例止损(全部持仓)
CheckBreakoutBalanceExit(); CheckBreakoutBatchTakeProfit();
CheckBreakoutBalanceStopLoss();
// 4. 当前轮双向同向均满档 → 清仓或开新轮 // 4. 当前轮双向同向均满档 → 清仓或开新轮
CheckDualSideFullCapacity(); CheckDualSideFullCapacity();
// 5. 突破信号(破极值开仓,每极值点各限1笔) // 5. 突破回落反向(破极值后K线收盘回到极值内)
CheckPullbackReverseSignals();
// 6. 突破信号(破极值开仓,每极值点各限1笔,与回落反向独立)
CheckBreakoutSignals(); CheckBreakoutSignals();
} }
@@ -479,10 +537,16 @@ void UpdateLatestValidWave()
latest_wave.update_time = extremes[i].time; latest_wave.update_time = extremes[i].time;
// 极值价格更新 → 该侧可再开1笔(与是否已有其它持仓无关) // 极值价格更新 → 该侧可再开1笔(与是否已有其它持仓无关)
if(high != prev_high) if(high != prev_high) {
latest_wave.high_breakout_used = false; 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_breakout_used = false;
latest_wave.low_pullback_reverse_used = false;
g_low_pullback_watch.active = false;
}
// 绘制最新有效波段 // 绘制最新有效波段
if(InpShowMarkers) { if(InpShowMarkers) {
@@ -625,22 +689,22 @@ ENUM_ORDER_TYPE RandomBreakoutOrderType()
int ParseRoundFromComment(const string &comment) int ParseRoundFromComment(const string &comment)
{ {
const string prefix = "[突破-R"; if(StringFind(comment, "[突破") != 0)
const int i = StringFind(comment, prefix); return 0;
if(i == 0) { const int i_r = StringFind(comment, "-R");
const int start = i + StringLen(prefix); if(i_r < 0)
const int end_br = StringFind(comment, "]", start);
if(end_br > start)
return (int)StringToInteger(StringSubstr(comment, start, end_br - start));
}
if(StringFind(comment, "[突破]") == 0)
return 1; 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) bool IsEaBreakoutComment(const string &comment)
{ {
return (ParseRoundFromComment(comment) > 0); return (StringFind(comment, "[突破") == 0);
} }
string FormatBreakoutComment(const int round) string FormatBreakoutComment(const int round)
@@ -650,10 +714,202 @@ string FormatBreakoutComment(const int round)
return StringFormat("[突破-R%d]", 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() void ResetBreakoutWaveEntryFlags()
{ {
latest_wave.high_breakout_used = false; latest_wave.high_breakout_used = false;
latest_wave.low_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() int CountAllBreakoutPositions()
@@ -715,16 +971,27 @@ double TotalLotsInDirection(const long pos_type)
return sum; 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) bool IsDirectionFullActive(const long pos_type)
{ {
const double cur = TotalLotsInDirection(pos_type); return IsSameDirectionTierSlotsFull(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);
} }
void CheckDualSideFullCapacity() void CheckDualSideFullCapacity()
@@ -842,26 +1109,53 @@ double NormalizeVolume(double lots)
return lots; return lots;
} }
// tier_index: 0=该方向首笔, 1=第2笔…;grp=tier/N,组内手数相同,每组=基准+grp×加码 bool InitTierLotsFromInput()
double LotForSameDirectionTier(const int tier_index, const double base_lot)
{ {
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; return 0.0;
const int n = MathMax(1, InpMartingaleTierInterval); double vol = g_tier_lots[tier_index];
const int grp = tier_index / n; if(InpMaxLots > 0.0)
if(InpMartingaleAddLotBoost <= 0.0) vol = MathMin(vol, InpMaxLots);
return NormalizeVolume(base_lot); return NormalizeVolume(vol);
double vol = base_lot + (double)grp * InpMartingaleAddLotBoost;
return NormalizeVolume(MathMin(vol, InpMaxLots));
} }
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
//| 按同向持仓笔数(档)计算本笔手数 | //| 按同向已有笔数取下一档手数(tier=0为第1笔) |
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
double CalculateLotSize(const long pos_type) double CalculateLotSize(const long pos_type)
{ {
const int tier = CountBreakoutPositions(pos_type); const int tier = CountBreakoutPositions(pos_type);
return LotForSameDirectionTier(tier, InpFixedLots); return LotForTierIndex(tier);
} }
bool GetLastSameDirectionOpenPrice(const long pos_type, double &last_open) bool GetLastSameDirectionOpenPrice(const long pos_type, double &last_open)
@@ -964,8 +1258,146 @@ bool CloseAllBreakoutPositions(const string reason)
return ok && n > 0; 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) if(CountAllBreakoutPositions() == 0)
return; return;
@@ -974,23 +1406,10 @@ void CheckBreakoutBalanceExit()
return; return;
const double fpl = TotalBreakoutFloatingPL(); const double fpl = TotalBreakoutFloatingPL();
const double sl_need = balance * (InpStopLossBalancePct / 100.0);
if(InpTakeProfitBalancePct > 0.0) { if(fpl <= -sl_need)
const double tp_need = balance * (InpTakeProfitBalancePct / 100.0); CloseAllBreakoutPositions(
if(fpl >= tp_need) { StringFormat("止损:浮亏%.2f达结余%.2f%%", fpl, InpStopLossBalancePct));
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));
}
}
} }
void SyncBreakoutBarLock(const bool for_high_extreme) 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) if(lots <= 0.0)
return false; return false;
const double cur_lots = TotalLotsInDirection(pos_type); if(lots <= 0.0) {
if(cur_lots + lots > InpMaxLots + 1e-8) { Print("【突破】开仓跳过 - 同向已达最大档数 ", g_tier_lots_count);
Print("【突破】开仓跳过 - 同向合计将超上限 ", InpMaxLots,
" 当前:", cur_lots, " 拟开:", lots);
return false; 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(!IsSameDirMinSpacingSatisfied(pos_type)) {
if(InpShowDebugInfo) { if(InpShowDebugInfo) {
@@ -1072,10 +1497,9 @@ bool OpenBreakoutPosition(ENUM_ORDER_TYPE order_type, double wave_high, double w
} }
MarkBreakoutBarOpened(from_high_extreme); MarkBreakoutBarOpened(from_high_extreme);
const int tier = CountBreakoutPositions(pos_type) - 1; const int tier = CountBreakoutPositions(pos_type);
const int grp = (tier >= 0) ? (tier / MathMax(1, InpMartingaleTierInterval)) : 0;
Print("【突破】开仓成功 ", (order_type == ORDER_TYPE_BUY ? "多" : "空"), 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); " 备注:", comment, " 波段 H:", wave_high, " L:", wave_low);
return true; 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 | | 波段 | K线周期 / MA周期 | M1 / 14 |
| 波段 | 最小/最大波段% | 0.1 / **1.0** | | 波段 | 最小/最大波段% | 0.1 / **0.25** |
| 波段 | 反向突破容忍度% | **0.05** | | 波段 | 反向突破容忍度% | **0.05** |
| 交易 | 开仓方向 | **顺势**(破高多/破低空) | | 交易 | 开仓方向 | **随机** |
| 交易 | 止盈:浮盈达结余比例(%) | **1**0=关) | | 交易 | 按批统盈点数 / 止损% | **300** / **30** |
| 交易 | 止损:浮亏达结余比例(%) | **30**0=关) | | 交易 | 回落反向 / 监测K线数 | **开** / **3** |
| 仓位 | 基准手数 / 每组+ / 档间隔N / 同向上限 / 同向最小间距% | **0.5** / **2** / 5 / **30** / **0.1** | | 仓位 | 各档手数(19档) | 见下 |
| 仓位 | 双向满档后处理 | **清仓**(平掉全部 EA 持仓) | | 仓位 | 同向最大合计 / 最小间距% / 满档后 | 99 / **0** / **清仓** |
| 调试 | 调试信息 / 极值标记 / Magic / 禁手工单 | false / true / 20260520 / true | | 调试 | 调试 / 极值标记 / 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]`
### 同向加码手数(类马丁) - 突破开仓与回落反向共用档位手数;列表长度 = 同向最多开仓笔数。
- 按批统盈、结余%止损、双向满档逻辑见 EA 输入注释。
- 按**当前轮、本方向已有笔数**定档。
- `组号 = tier ÷ 档间隔N`**本笔手数** = `基准 + 组号 × 每组+`
- **当前轮**该方向合计不得超过 `同向最大合计手数`
### 同向最小开仓间距
- **0 = 不限制**;仅相对**当前轮**最近一笔同向开仓价判断。
### 开仓方向
| 选项 | 破高 | 破低 |
|------|------|------|
| **顺势** | 多 | 空 |
| **反向** | 空 | 多 |
| **随机** | 每次突破独立随机 | |
## 双向满档(当前轮多空均达同向上限)
当**当前轮**多单、空单均**无法再开下一笔**(合计已达上限,或「当前合计 + 下一档手数」会超限,例如 19.5+4.5>20)时触发一次:
| 模式 | 行为 |
|------|------|
| **清仓**(默认) | 平掉本 EA 全部持仓(各轮一并平掉),轮次重置为 1,极值标记重置 |
| **不处理旧仓,开始下一轮** | 旧仓保留;轮次 +1;新单从基准手数重新加码 |
触发后需等当前轮任一侧未满档,才会在再次双向满档时重复处理。
## 平仓(结余比例)
- 统计**全部轮次**持仓浮盈浮亏(含 swap)。
- 止盈 / 止损达标 → **全部清仓**(各轮一并平掉)。
## 手工单
- `禁止手工单=true` 时自动平掉本品种 Magic=0 的手工单。
Binary file not shown.