feat: 突破EA
This commit is contained in:
@@ -1,21 +0,0 @@
|
||||
# 均线突破策略
|
||||
|
||||
## 基础概念
|
||||
- 平台:MQL5
|
||||
- 品种:XAU
|
||||
|
||||
## 辅助指标
|
||||
- 移动平均线(Moving Average)
|
||||
- 周期默认设置为:14
|
||||
|
||||
## 核心逻辑
|
||||
- K线周期:支持参数设置,默认为1min
|
||||
- 突破K线:开仓价在均线之下,收盘价在均线之上,称之为多单突破K线,反之亦然
|
||||
- 出现多单突破K线时,开多单,反之开空单,开仓时机是基于K线收盘价
|
||||
- 每单都是独立的,不限制单方向只能持有一单,止损放在突破K线的最低点或最高点,具体取决于开单方向
|
||||
- 止盈基于盈亏比的设置
|
||||
|
||||
## 补充逻辑
|
||||
- 是否复利,支持参数设置
|
||||
- 移动止损:支持设置为开单价格的百分比
|
||||
|
||||
@@ -1,420 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 均线突破策略 EA — 逻辑见同目录 README.md |
|
||||
//| MA(默认14) 突破收盘确认;独立持仓;SL/TP;移动止损=开仓价百分比间距。 |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "zyb-ea"
|
||||
#property version "2.20"
|
||||
#property description "MA突破:SL=突破K极值;TP=盈亏比;移动止损=开仓价×百分比间距。"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
CTrade g_trade;
|
||||
|
||||
int g_hMa = INVALID_HANDLE;
|
||||
static datetime g_lastWorkBar = 0;
|
||||
|
||||
//------------------------------------------------------------
|
||||
input group "工作区"
|
||||
input ENUM_TIMEFRAMES InpWorkTF = PERIOD_M1; // K 线周期(默认 1 分钟)
|
||||
input int InpSlippage = 30; // 滑点(点)
|
||||
input int InpMaxSpreadPoints = 0; // 最大点差(点,0=不限制)
|
||||
input long InpMagic = 20260509; // Magic 识别码
|
||||
|
||||
input group "移动平均线"
|
||||
input int InpMAPeriod = 14; // MA 周期
|
||||
input int InpMAShift = 0; // MA 位移
|
||||
input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // MA 算法
|
||||
input ENUM_APPLIED_PRICE InpMAPrice = PRICE_CLOSE; // 应用于
|
||||
|
||||
input group "下单"
|
||||
input double InpLot = 0.01; // 基准开仓手数(复利关闭时即为实际手数)
|
||||
|
||||
input group "止盈(盈亏比)"
|
||||
input double InpRewardRiskRatio = 2.0; // 盈亏比:止盈距离 = 入场相对止损的风险宽度 × 本值(≤0 表示不设止盈)
|
||||
|
||||
input group "移动止损"
|
||||
input double InpTrailPercentOfOpen = 0.0; // 跟踪间距 = 开仓价 × 本%/100(0=关闭);多:SL=BID−间距;空:SL=ASK+间距
|
||||
|
||||
input group "复利"
|
||||
input bool InpUseCompound = false; // 开启后按账户净值相对参考净值缩放手数
|
||||
input double InpCompoundRefEquity = 10000.0; // 参考净值:实际手数 ≈ InpLot × (当前净值 / 参考净值)
|
||||
|
||||
input group "其它"
|
||||
input bool InpLog = true; // 是否打印日志
|
||||
|
||||
//------------------------------------------------------------
|
||||
double MinLot() { return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); }
|
||||
double MaxLot() { return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); }
|
||||
double LotStep() { return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); }
|
||||
|
||||
double NormalizeVolumeVal(const double v)
|
||||
{
|
||||
const double lo = MinLot();
|
||||
const double hi = MaxLot();
|
||||
const double st = LotStep();
|
||||
if(st <= 0.0 || hi < lo)
|
||||
return v;
|
||||
if(v < lo - 1e-12)
|
||||
return 0.0;
|
||||
double x = MathMin(v, hi);
|
||||
x = MathFloor(x / st + 1e-12) * st;
|
||||
return x;
|
||||
}
|
||||
|
||||
double CalcOrderLot()
|
||||
{
|
||||
double lot = InpLot;
|
||||
if(InpUseCompound && InpCompoundRefEquity > 1e-8)
|
||||
{
|
||||
const double eq = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
lot = InpLot * (eq / InpCompoundRefEquity);
|
||||
}
|
||||
return NormalizeVolumeVal(lot);
|
||||
}
|
||||
|
||||
int SpreadPoints()
|
||||
{
|
||||
return (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
||||
}
|
||||
|
||||
double StopsMinDistancePrice()
|
||||
{
|
||||
const double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
const int st = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const int fr = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||||
return (double)(st + fr) * pt;
|
||||
}
|
||||
|
||||
// 市价多单:SL 须在 Bid 下方且满足最小止损距离
|
||||
bool IsBuyStopLossValid(const double sl)
|
||||
{
|
||||
if(sl <= 0.0)
|
||||
return false;
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
const double need = StopsMinDistancePrice();
|
||||
const double slN = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
||||
if(slN >= bid - need)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 市价空单:SL 须在 Ask 上方且满足最小止损距离
|
||||
bool IsSellStopLossValid(const double sl)
|
||||
{
|
||||
if(sl <= 0.0)
|
||||
return false;
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
const double need = StopsMinDistancePrice();
|
||||
const double slN = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
||||
if(slN <= ask + need)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 多单止盈须在 Ask 上方且满足最小距离
|
||||
bool IsBuyTakeProfitValid(const double tp)
|
||||
{
|
||||
if(tp <= 0.0)
|
||||
return false;
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
const double need = StopsMinDistancePrice();
|
||||
if(tp <= ask + need)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 空单止盈须在 Bid 下方且满足最小距离
|
||||
bool IsSellTakeProfitValid(const double tp)
|
||||
{
|
||||
if(tp <= 0.0)
|
||||
return false;
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
const double need = StopsMinDistancePrice();
|
||||
if(tp >= bid - need)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 风险宽 = |入场(Ask) − SL|;TP = Ask + 风险×RR
|
||||
bool CalcBuyTpByRewardRisk(const double sl, double &tp)
|
||||
{
|
||||
tp = 0.0;
|
||||
if(InpRewardRiskRatio <= 0.0)
|
||||
return true;
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
const double risk = ask - sl;
|
||||
if(risk <= 1e-12)
|
||||
return false;
|
||||
const int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
tp = NormalizeDouble(ask + risk * InpRewardRiskRatio, dg);
|
||||
return IsBuyTakeProfitValid(tp);
|
||||
}
|
||||
|
||||
// 风险宽 = SL − Ask;TP = Ask − 风险×RR
|
||||
bool CalcSellTpByRewardRisk(const double sl, double &tp)
|
||||
{
|
||||
tp = 0.0;
|
||||
if(InpRewardRiskRatio <= 0.0)
|
||||
return true;
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
const double risk = sl - ask;
|
||||
if(risk <= 1e-12)
|
||||
return false;
|
||||
const int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
tp = NormalizeDouble(ask - risk * InpRewardRiskRatio, dg);
|
||||
return IsSellTakeProfitValid(tp);
|
||||
}
|
||||
|
||||
/// 按开仓价百分比为间距跟踪止损(仅收紧:上移多单 SL、下移空单 SL)
|
||||
void TryTrailingStopByOpenPercent()
|
||||
{
|
||||
if(InpTrailPercentOfOpen <= 0.0)
|
||||
return;
|
||||
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
|
||||
const int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
const double need = StopsMinDistancePrice();
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
const double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
|
||||
for(int i = (int)PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0 || !PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
continue;
|
||||
|
||||
const double openPx = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double trailDist = openPx * (InpTrailPercentOfOpen / 100.0);
|
||||
if(trailDist <= 1e-12)
|
||||
continue;
|
||||
|
||||
const double slOld = PositionGetDouble(POSITION_SL);
|
||||
const double tpOld = PositionGetDouble(POSITION_TP);
|
||||
const long typ = (long)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
if(slOld <= 0.0)
|
||||
continue;
|
||||
|
||||
if(typ == POSITION_TYPE_BUY)
|
||||
{
|
||||
double newSL = NormalizeDouble(bid - trailDist, dg);
|
||||
if(newSL <= slOld + pt * 0.5)
|
||||
continue;
|
||||
if(newSL >= bid - need)
|
||||
continue;
|
||||
if(tpOld > 0.0 && newSL >= tpOld)
|
||||
continue;
|
||||
if(!g_trade.PositionModify(ticket, newSL, tpOld))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("移动止损(多)失败 ticket=", ticket, " err=", GetLastError(),
|
||||
" newSL=", newSL, " oldSL=", slOld);
|
||||
}
|
||||
}
|
||||
else if(typ == POSITION_TYPE_SELL)
|
||||
{
|
||||
double newSL = NormalizeDouble(ask + trailDist, dg);
|
||||
if(newSL >= slOld - pt * 0.5)
|
||||
continue;
|
||||
if(newSL <= ask + need)
|
||||
continue;
|
||||
if(tpOld > 0.0 && newSL <= tpOld)
|
||||
continue;
|
||||
if(!g_trade.PositionModify(ticket, newSL, tpOld))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("移动止损(空)失败 ticket=", ticket, " err=", GetLastError(),
|
||||
" newSL=", newSL, " oldSL=", slOld);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
bool CopyBar1OHLCMA(double &o1, double &c1, double &l1, double &h1, double &ma1)
|
||||
{
|
||||
double o[], c[], l[], h[];
|
||||
ArraySetAsSeries(o, true);
|
||||
ArraySetAsSeries(c, true);
|
||||
ArraySetAsSeries(l, true);
|
||||
ArraySetAsSeries(h, true);
|
||||
if(CopyOpen(_Symbol, InpWorkTF, 1, 1, o) != 1)
|
||||
return false;
|
||||
if(CopyHigh(_Symbol, InpWorkTF, 1, 1, h) != 1)
|
||||
return false;
|
||||
if(CopyLow(_Symbol, InpWorkTF, 1, 1, l) != 1)
|
||||
return false;
|
||||
if(CopyClose(_Symbol, InpWorkTF, 1, 1, c) != 1)
|
||||
return false;
|
||||
double m[];
|
||||
ArraySetAsSeries(m, true);
|
||||
if(CopyBuffer(g_hMa, 0, 1, 1, m) != 1)
|
||||
return false;
|
||||
o1 = o[0];
|
||||
c1 = c[0];
|
||||
l1 = l[0];
|
||||
h1 = h[0];
|
||||
ma1 = m[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 新 K 线开盘:上一根已收盘 K 为突破 K;每单独立,带 SL / 可选 RR 止盈
|
||||
void OnNewClosedBar()
|
||||
{
|
||||
if(g_hMa == INVALID_HANDLE)
|
||||
return;
|
||||
|
||||
double o1 = 0.0, c1 = 0.0, l1 = 0.0, h1 = 0.0, ma1 = 0.0;
|
||||
if(!CopyBar1OHLCMA(o1, c1, l1, h1, ma1))
|
||||
return;
|
||||
|
||||
const bool longBreak = (o1 < ma1 && c1 > ma1);
|
||||
const bool shortBreak = (o1 > ma1 && c1 < ma1);
|
||||
|
||||
if(!longBreak && !shortBreak)
|
||||
return;
|
||||
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
|
||||
const double vol = CalcOrderLot();
|
||||
if(vol < MinLot() - 1e-12)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("手数过小,跳过开仓");
|
||||
return;
|
||||
}
|
||||
|
||||
const int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
|
||||
if(longBreak)
|
||||
{
|
||||
double sl = NormalizeDouble(l1, dg);
|
||||
if(!IsBuyStopLossValid(sl))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("多单突破: 止损价不满足券商最小距离或无效,跳过 SL=", sl,
|
||||
" Bid=", SymbolInfoDouble(_Symbol, SYMBOL_BID),
|
||||
" 突破K最低=", l1);
|
||||
return;
|
||||
}
|
||||
double tp = 0.0;
|
||||
if(InpRewardRiskRatio > 0.0)
|
||||
{
|
||||
if(!CalcBuyTpByRewardRisk(sl, tp))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("多单突破: 止盈(盈亏比 ", InpRewardRiskRatio, ") 无效或距离不足,跳过");
|
||||
return;
|
||||
}
|
||||
}
|
||||
const string cmt = "突破-多";
|
||||
if(!g_trade.Buy(vol, _Symbol, 0.0, sl, tp, cmt))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("开多单失败 err=", GetLastError(), " SL=", sl, " TP=", tp);
|
||||
}
|
||||
else if(InpLog)
|
||||
Print("多单突破 独立开仓 手数=", vol, " SL=", sl, " TP=", tp,
|
||||
" RR=", InpRewardRiskRatio,
|
||||
" O=", o1, " C=", c1, " MA=", ma1);
|
||||
}
|
||||
else if(shortBreak)
|
||||
{
|
||||
double sl = NormalizeDouble(h1, dg);
|
||||
if(!IsSellStopLossValid(sl))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("空单突破: 止损价不满足券商最小距离或无效,跳过 SL=", sl,
|
||||
" Ask=", SymbolInfoDouble(_Symbol, SYMBOL_ASK),
|
||||
" 突破K最高=", h1);
|
||||
return;
|
||||
}
|
||||
double tp = 0.0;
|
||||
if(InpRewardRiskRatio > 0.0)
|
||||
{
|
||||
if(!CalcSellTpByRewardRisk(sl, tp))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("空单突破: 止盈(盈亏比 ", InpRewardRiskRatio, ") 无效或距离不足,跳过");
|
||||
return;
|
||||
}
|
||||
}
|
||||
const string cmt = "突破-空";
|
||||
if(!g_trade.Sell(vol, _Symbol, 0.0, sl, tp, cmt))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("开空单失败 err=", GetLastError(), " SL=", sl, " TP=", tp);
|
||||
}
|
||||
else if(InpLog)
|
||||
Print("空单突破 独立开仓 手数=", vol, " SL=", sl, " TP=", tp,
|
||||
" RR=", InpRewardRiskRatio,
|
||||
" O=", o1, " C=", c1, " MA=", ma1);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
int OnInit()
|
||||
{
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
const int filling = (int)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
|
||||
if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
else if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
else
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||||
|
||||
g_hMa = iMA(_Symbol, InpWorkTF, InpMAPeriod, InpMAShift, InpMAMethod, InpMAPrice);
|
||||
if(g_hMa == INVALID_HANDLE)
|
||||
{
|
||||
Print("初始化 MA 句柄失败");
|
||||
return INIT_FAILED;
|
||||
}
|
||||
g_lastWorkBar = 0;
|
||||
if(InpLog)
|
||||
Print("均线突破 EA v2.2: ", _Symbol,
|
||||
" 周期=", EnumToString(InpWorkTF),
|
||||
" MA=", InpMAPeriod,
|
||||
" 基准手数=", InpLot,
|
||||
" 盈亏比RR=", InpRewardRiskRatio,
|
||||
" 移动止损%=", InpTrailPercentOfOpen,
|
||||
" 复利=", (InpUseCompound ? "开" : "关"),
|
||||
(InpUseCompound ? StringFormat(" 参考净值=%.2f 当前手数≈%.4f", InpCompoundRefEquity, CalcOrderLot()) : ""),
|
||||
" 独立持仓 SL=突破K TP=风险×RR");
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_hMa != INVALID_HANDLE)
|
||||
{
|
||||
IndicatorRelease(g_hMa);
|
||||
g_hMa = INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(InpMaxSpreadPoints > 0 && SpreadPoints() > InpMaxSpreadPoints)
|
||||
return;
|
||||
|
||||
TryTrailingStopByOpenPercent();
|
||||
|
||||
const datetime bar0 = iTime(_Symbol, InpWorkTF, 0);
|
||||
if(bar0 != 0 && bar0 != g_lastWorkBar)
|
||||
{
|
||||
g_lastWorkBar = bar0;
|
||||
OnNewClosedBar();
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
+308
-244
@@ -3,11 +3,17 @@
|
||||
//| 突破交易策略 - 完整交易版本 |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Breakout Strategy"
|
||||
#property version "1.00"
|
||||
#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)
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 输入参数 |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -15,20 +21,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 = 10.0; // 最大波段阈值百分比(%)
|
||||
input double InpPullbackTolerance = 0.0; // 反向突破容忍度(%) 0=不容忍
|
||||
input int InpMinWaveBars = 3; // 有效波段最少K线数(含两端极值所在K)
|
||||
|
||||
input group "=== 风险管理参数 ==="
|
||||
input double InpStopLossPercent = 0.02; // 止损百分比(%)
|
||||
input int InpMinStopLossPoints = 10; // 最小止损点数
|
||||
input double InpRiskRewardRatio = 1.2; // 盈亏比
|
||||
input bool InpUseTrailingStop = false; // 使用移动止损
|
||||
input int InpStopLossPoints = 200; // 止损点数
|
||||
input int InpTakeProfitPoints = 300; // 止盈点数
|
||||
input bool InpUseTrailingStop = true; // 使用移动止损
|
||||
|
||||
input group "=== 仓位管理参数 ==="
|
||||
input bool InpUseCompounding = true; // 使用复利模式
|
||||
input double InpFixedLots = 0.01; // 固定手数
|
||||
input double InpLotsPer500 = 0.05; // 每500$开仓手数(复利模式)
|
||||
input int InpMaxPositions = 99; // 最大开仓手数
|
||||
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 "=== 连续亏损保护 ==="
|
||||
@@ -59,6 +66,9 @@ struct ValidWaveInfo {
|
||||
|
||||
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表示未冷冻)
|
||||
@@ -74,14 +84,24 @@ struct ExtremePoint {
|
||||
|
||||
// 函数声明
|
||||
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 CheckOpenSignals();
|
||||
bool OpenPosition(ENUM_ORDER_TYPE order_type, double wave_high, double wave_low, double threshold_points);
|
||||
double CalculateLotSize();
|
||||
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();
|
||||
@@ -117,20 +137,24 @@ int OnInit()
|
||||
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("止损:", InpStopLossPercent, "% (最小", InpMinStopLossPoints, "点) | 盈亏比:", InpRiskRewardRatio);
|
||||
Print("止损:", InpStopLossPoints, "点 | 止盈:", InpTakeProfitPoints, "点");
|
||||
Print("移动止损:", (InpUseTrailingStop ? "启用" : "禁用"));
|
||||
Print("最大开仓手数:", InpMaxPositions);
|
||||
Print("最大持仓笔数:", InpMaxPositions, " 单笔最大手数:", InpMaxLots);
|
||||
Print("单方向持仓限制:", (InpOnePositionPerDirection ? "启用 (每方向最多1单)" : "禁用"));
|
||||
if(InpConsecutiveLosses > 0 && InpFreezeBarCount > 0)
|
||||
Print("连续亏损保护: 启用 (", InpConsecutiveLosses, "次亏损→冷冻", InpFreezeBarCount, "根K线)");
|
||||
else
|
||||
Print("连续亏损保护: 禁用");
|
||||
Print("手数模式:", (InpUseCompounding ? "复利" : "固定"),
|
||||
InpUseCompounding ? StringFormat(" (每500$开%.2f手)", InpLotsPer500) : StringFormat(" (%.2f手)", InpFixedLots));
|
||||
if(InpLotSizeMode == LOT_SIZE_RISK_PERCENT)
|
||||
Print("手数模式: 结余风险比例 ", InpRiskPercent, "% (按止损距离反推手数)");
|
||||
else
|
||||
Print("手数模式: 固定手数 ", InpFixedLots);
|
||||
Print("开仓方式: 突破挂单 (高点BUY STOP / 低点SELL STOP)");
|
||||
Print("禁止手工单:", (InpCloseManualOrders ? "启用 (自动平掉手工单)" : "禁用"));
|
||||
Print("========================================");
|
||||
|
||||
@@ -147,6 +171,7 @@ void OnDeinit(const int reason)
|
||||
|
||||
// 删除所有标记
|
||||
ObjectsDeleteAll(0, "ValidWave_");
|
||||
CancelEaPendingOrders(true, true);
|
||||
|
||||
Print("突破交易策略EA已卸载");
|
||||
}
|
||||
@@ -158,7 +183,22 @@ 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;
|
||||
|
||||
@@ -236,8 +276,8 @@ void OnTick()
|
||||
// 2. 更新最新有效波段
|
||||
UpdateLatestValidWave();
|
||||
|
||||
// 3. 检查开仓信号
|
||||
CheckOpenSignals();
|
||||
// 3. 同步突破挂单(BUY STOP@高 / SELL STOP@低)
|
||||
SyncBreakoutPendingOrders();
|
||||
|
||||
// 4. 管理已有持仓
|
||||
ManagePositions();
|
||||
@@ -246,6 +286,26 @@ void OnTick()
|
||||
//+------------------------------------------------------------------+
|
||||
//| 更新最新有效波段 |
|
||||
//+------------------------------------------------------------------+
|
||||
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);
|
||||
@@ -455,7 +515,8 @@ void UpdateLatestValidWave()
|
||||
double max_threshold = (base_price * InpMaxWavePercent / 100.0) / _Point;
|
||||
|
||||
// 波段必须在最小和最大阈值之间才是有效波段
|
||||
if(price_diff_points >= min_threshold && price_diff_points <= max_threshold) {
|
||||
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;
|
||||
}
|
||||
@@ -477,7 +538,8 @@ void UpdateLatestValidWave()
|
||||
double max_threshold = (base_price * InpMaxWavePercent / 100.0) / _Point;
|
||||
|
||||
// 波段必须在最小和最大阈值之间才是有效波段
|
||||
if(price_diff_points >= min_threshold && price_diff_points <= max_threshold) {
|
||||
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);
|
||||
@@ -506,6 +568,7 @@ void UpdateLatestValidWave()
|
||||
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));
|
||||
}
|
||||
@@ -632,39 +695,143 @@ void FilterBreakouts(const int &breakout_bars[], const int &breakout_types[],
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 检查开仓信号 |
|
||||
//| 突破挂单管理 |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckOpenSignals()
|
||||
void CancelEaPendingOrders(const bool cancel_buy_stop, const bool cancel_sell_stop)
|
||||
{
|
||||
if(!latest_wave.exists)
|
||||
return;
|
||||
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(InpConsecutiveLosses > 0 && InpFreezeBarCount > 0 && freeze_bar_index > 0)
|
||||
{
|
||||
int current_bars = Bars(_Symbol, InpTimeframe);
|
||||
if(current_bars < freeze_bar_index)
|
||||
{
|
||||
// 仍在冷冻期内 - 静默拒绝开仓
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 冷冻期结束
|
||||
if(freeze_bar_index > 0)
|
||||
{
|
||||
Print("【连续亏损保护】冷冻解除,恢复交易");
|
||||
consecutive_loss_count = 0;
|
||||
freeze_bar_index = 0;
|
||||
freeze_until_time = 0;
|
||||
}
|
||||
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; // 空单数量
|
||||
int buy_positions = 0;
|
||||
int sell_positions = 0;
|
||||
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
if(!PositionSelectByTicket(PositionGetTicket(i)))
|
||||
@@ -673,11 +840,8 @@ void CheckOpenSignals()
|
||||
continue;
|
||||
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
|
||||
continue;
|
||||
|
||||
total_positions++;
|
||||
|
||||
// 统计各方向持仓数量
|
||||
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
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)
|
||||
@@ -685,226 +849,131 @@ void CheckOpenSignals()
|
||||
}
|
||||
|
||||
if(total_positions >= InpMaxPositions) {
|
||||
CancelEaPendingOrders(true, true);
|
||||
if(InpShowDebugInfo)
|
||||
Print("已达最大持仓数量限制: ", total_positions, "/", InpMaxPositions);
|
||||
Print("已达最大持仓笔数,撤销突破挂单: ", total_positions, "/", InpMaxPositions);
|
||||
return;
|
||||
}
|
||||
|
||||
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
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);
|
||||
|
||||
// 计算当前波段的实际阈值(百分比模式:取高低点的平均值作为基准)
|
||||
double base_price = (latest_wave.high_price + latest_wave.low_price) / 2.0;
|
||||
double wave_threshold_points = (base_price * InpMinWavePercent / 100.0) / _Point;
|
||||
|
||||
// 检查多单信号:突破高点(且该高点未使用过)
|
||||
if(!latest_wave.high_used && current_price > latest_wave.high_price) {
|
||||
// 如果启用了单方向持仓限制,检查多单数量
|
||||
if(InpOnePositionPerDirection && buy_positions >= 1) {
|
||||
// 高点 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_positions);
|
||||
Print("价格已越过波段高点,暂不挂BUY STOP Ask:", ask, " 高:", wave_high);
|
||||
} else {
|
||||
if(InpShowDebugInfo)
|
||||
Print("检测到多单信号 - 价格:", current_price, " 突破高点:", latest_wave.high_price);
|
||||
|
||||
if(OpenPosition(ORDER_TYPE_BUY, latest_wave.high_price, latest_wave.low_price, wave_threshold_points)) {
|
||||
latest_wave.high_used = true; // 标记高点已使用,该波段高点失效
|
||||
if(InpShowDebugInfo)
|
||||
Print("高点已使用,等待新的有效波段");
|
||||
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);
|
||||
}
|
||||
|
||||
// 检查空单信号:突破低点(且该低点未使用过)
|
||||
if(!latest_wave.low_used && current_price < latest_wave.low_price) {
|
||||
// 如果启用了单方向持仓限制,检查空单数量
|
||||
if(InpOnePositionPerDirection && sell_positions >= 1) {
|
||||
// 低点 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_positions);
|
||||
Print("价格已越过波段低点,暂不挂SELL STOP Bid:", bid, " 低:", wave_low);
|
||||
} else {
|
||||
if(InpShowDebugInfo)
|
||||
Print("检测到空单信号 - 价格:", current_price, " 突破低点:", latest_wave.low_price);
|
||||
|
||||
if(OpenPosition(ORDER_TYPE_SELL, latest_wave.high_price, latest_wave.low_price, wave_threshold_points)) {
|
||||
latest_wave.low_used = true; // 标记低点已使用,该波段低点失效
|
||||
if(InpShowDebugInfo)
|
||||
Print("低点已使用,等待新的有效波段");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 开仓 |
|
||||
//| 止损/止盈距离(点数) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool OpenPosition(ENUM_ORDER_TYPE order_type, double wave_high, double wave_low, double threshold_points)
|
||||
double GetStopLossOffset()
|
||||
{
|
||||
double lots = CalculateLotSize();
|
||||
if(lots <= 0)
|
||||
return false;
|
||||
if(InpStopLossPoints <= 0)
|
||||
return 0.0;
|
||||
return (double)InpStopLossPoints * _Point;
|
||||
}
|
||||
|
||||
// 生成备注信息:盈亏比和最小止损点数
|
||||
string comment = StringFormat("R%.1f SL%d",
|
||||
InpRiskRewardRatio,
|
||||
InpMinStopLossPoints);
|
||||
double GetTakeProfitOffset()
|
||||
{
|
||||
if(InpTakeProfitPoints <= 0)
|
||||
return 0.0;
|
||||
return (double)InpTakeProfitPoints * _Point;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
double StopLossAmountFromPositionComment(const string &comment)
|
||||
{
|
||||
return GetStopLossOffset();
|
||||
}
|
||||
|
||||
// 先以市价开仓(不带SL/TP),成交后再根据实际成交价设置SL/TP
|
||||
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);
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
//| 手数规范化 |
|
||||
//+------------------------------------------------------------------+
|
||||
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(!result) {
|
||||
Print("开仓失败: ", trade.ResultRetcode(), " - ", trade.ResultRetcodeDescription());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待持仓信息更新
|
||||
Sleep(100);
|
||||
|
||||
// 通过符号和魔术号查找刚开的持仓
|
||||
ulong ticket = 0;
|
||||
double open_price = 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;
|
||||
|
||||
// 找到最新的持仓(没有SL/TP的)
|
||||
if(PositionGetDouble(POSITION_SL) == 0 && PositionGetDouble(POSITION_TP) == 0) {
|
||||
ticket = PositionGetTicket(i);
|
||||
open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ticket == 0) {
|
||||
Print("无法找到刚开的持仓");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 基于实际成交价计算止损金额
|
||||
double stop_loss_amount = open_price * InpStopLossPercent / 100.0;
|
||||
|
||||
// 确保止损金额不小于最小止损点数
|
||||
double min_stop_loss = InpMinStopLossPoints * _Point;
|
||||
if(stop_loss_amount < min_stop_loss) {
|
||||
stop_loss_amount = min_stop_loss;
|
||||
}
|
||||
|
||||
// 获取平台的最小止损距离
|
||||
int stops_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double min_stop_distance = stops_level * _Point;
|
||||
|
||||
// 获取当前市场价格
|
||||
double current_bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double current_ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
double sl = 0, tp = 0;
|
||||
|
||||
if(order_type == ORDER_TYPE_BUY) {
|
||||
// 先标准化止损价
|
||||
sl = NormalizeDouble(open_price - stop_loss_amount, _Digits);
|
||||
// 根据标准化后的实际止损金额重新计算止盈金额,确保盈亏比准确
|
||||
double actual_sl_amount = open_price - sl;
|
||||
double take_profit_amount = actual_sl_amount * InpRiskRewardRatio;
|
||||
tp = NormalizeDouble(open_price + take_profit_amount, _Digits);
|
||||
|
||||
// 检查止损距离(多单检查与Bid的距离)
|
||||
if(stops_level > 0 && (current_bid - sl) < min_stop_distance) {
|
||||
sl = NormalizeDouble(current_bid - min_stop_distance, _Digits);
|
||||
// 重新计算止盈
|
||||
actual_sl_amount = open_price - sl;
|
||||
take_profit_amount = actual_sl_amount * InpRiskRewardRatio;
|
||||
tp = NormalizeDouble(open_price + take_profit_amount, _Digits);
|
||||
Print("止损距离不足,已调整 - 新止损:", sl);
|
||||
}
|
||||
|
||||
// 检查止盈距离
|
||||
if(stops_level > 0 && (tp - current_ask) < min_stop_distance) {
|
||||
tp = NormalizeDouble(current_ask + min_stop_distance, _Digits);
|
||||
Print("止盈距离不足,已调整 - 新止盈:", tp);
|
||||
}
|
||||
} else {
|
||||
// 先标准化止损价
|
||||
sl = NormalizeDouble(open_price + stop_loss_amount, _Digits);
|
||||
// 根据标准化后的实际止损金额重新计算止盈金额,确保盈亏比准确
|
||||
double actual_sl_amount = sl - open_price;
|
||||
double take_profit_amount = actual_sl_amount * InpRiskRewardRatio;
|
||||
tp = NormalizeDouble(open_price - take_profit_amount, _Digits);
|
||||
|
||||
// 检查止损距离(空单检查与Ask的距离)
|
||||
if(stops_level > 0 && (sl - current_ask) < min_stop_distance) {
|
||||
sl = NormalizeDouble(current_ask + min_stop_distance, _Digits);
|
||||
// 重新计算止盈
|
||||
actual_sl_amount = sl - open_price;
|
||||
take_profit_amount = actual_sl_amount * InpRiskRewardRatio;
|
||||
tp = NormalizeDouble(open_price - take_profit_amount, _Digits);
|
||||
Print("止损距离不足,已调整 - 新止损:", sl);
|
||||
}
|
||||
|
||||
// 检查止盈距离
|
||||
if(stops_level > 0 && (current_bid - tp) < min_stop_distance) {
|
||||
tp = NormalizeDouble(current_bid - min_stop_distance, _Digits);
|
||||
Print("止盈距离不足,已调整 - 新止盈:", tp);
|
||||
}
|
||||
}
|
||||
|
||||
// 修改持仓的SL/TP
|
||||
if(!trade.PositionModify(ticket, sl, tp)) {
|
||||
Print("修改SL/TP失败 - Ticket:", ticket,
|
||||
" 错误码:", trade.ResultRetcode(),
|
||||
" 描述:", trade.ResultRetcodeDescription(),
|
||||
" 止损:", sl, " 止盈:", tp,
|
||||
" 平台最小距离:", stops_level, "点");
|
||||
return false;
|
||||
}
|
||||
|
||||
Print(order_type == ORDER_TYPE_BUY ? "开多单成功" : "开空单成功",
|
||||
" - 手数:", lots,
|
||||
" 波段:H:", wave_high, " L:", wave_low,
|
||||
" 实际成交价:", open_price,
|
||||
" 止损:", sl, "(", InpStopLossPercent, "%)",
|
||||
" 止盈:", tp, "(盈亏比", InpRiskRewardRatio, ")");
|
||||
|
||||
return true;
|
||||
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()
|
||||
double CalculateLotSize(const double wave_high, const double wave_low)
|
||||
{
|
||||
double lots = 0;
|
||||
double lots = InpFixedLots;
|
||||
|
||||
if(InpUseCompounding) {
|
||||
// 复利模式
|
||||
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
lots = NormalizeDouble((balance / 500.0) * InpLotsPer500, 2);
|
||||
} else {
|
||||
// 固定手数模式
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查手数限制
|
||||
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
||||
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
||||
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
||||
|
||||
if(lots < min_lot) lots = min_lot;
|
||||
if(lots > max_lot) lots = max_lot;
|
||||
|
||||
lots = MathFloor(lots / lot_step) * lot_step;
|
||||
|
||||
return lots;
|
||||
return NormalizeVolumeLots(lots);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -947,14 +1016,9 @@ void CheckTrailingStop(ulong ticket)
|
||||
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
|
||||
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
// 计算止损金额(开仓价的百分比)
|
||||
double stop_loss_amount = open_price * InpStopLossPercent / 100.0;
|
||||
|
||||
// 确保止损金额不小于最小止损点数
|
||||
double min_stop_loss = InpMinStopLossPoints * _Point;
|
||||
if(stop_loss_amount < min_stop_loss) {
|
||||
stop_loss_amount = min_stop_loss;
|
||||
}
|
||||
// 计算止损金额(有效波段区间 × 比例,备注中 WR 存区间点数)
|
||||
const string comment = PositionGetString(POSITION_COMMENT);
|
||||
const double stop_loss_amount = StopLossAmountFromPositionComment(comment);
|
||||
|
||||
// 计算浮盈
|
||||
double profit_amount = 0;
|
||||
|
||||
+61
-65
@@ -13,44 +13,68 @@
|
||||
## 核心概念
|
||||
- 突破K线:开盘价在均线下方,收盘价在均线上方,称之为多单突破K线,反之亦然
|
||||
- 极值点:相邻两根突破K线之间所有K线(包括突破K线)中的最高价或最低价称之为极值点
|
||||
- 有效波段:相邻两个极值点的价差达到波段阈值点数时,期间的所有K线(包含突破K线本身)组成的行情,称之为有效波段
|
||||
- 有效波段:相邻两个极值点的价差达到波段阈值点数,且**两极值之间至少包含 3 根 K 线**(含两端极值所在 K),期间所有 K 线组成有效波段
|
||||
|
||||
## 开仓逻辑
|
||||
- 价格突破有效波段的高点开多单,价格突破有效波点的低点开空单
|
||||
- 该开仓逻辑仅适用最新的一个有效波段
|
||||
- 止损和止盈基于开仓价的百分比计算,止盈 = 止损金额 × 盈亏比
|
||||
- 浮盈达到止损金额后,即可将止损移动至成本价
|
||||
- 识别最新有效波段后,在**波段高点挂 BUY STOP**、**波段低点挂 SELL STOP**(突破触发)
|
||||
- 挂单时一并设置 SL/TP(固定止损/止盈点数)
|
||||
- 该逻辑仅适用最新的一个有效波段;换波段时撤销旧挂单并重挂
|
||||
- 价格已越过极值(无法挂 STOP)时暂不挂单,等高/低侧标记使用后等待新波段
|
||||
- 浮盈达到止损点数对应距离后,即可将止损移动至成本价
|
||||
- 已经开仓后,继续监控新的有效波段突破,每单开仓后就与有效波段无关了
|
||||
|
||||
## 参数默认值对照表
|
||||
|
||||
| 分组 | 参数 | 默认 |
|
||||
|------|------|------|
|
||||
| 波段 | K线周期 / MA周期 | M1 / 14 |
|
||||
| 波段 | 最小/最大波段% | 0.1 / **10** |
|
||||
| 波段 | 反向突破容忍度% / 最少K线 | 0 / **3** |
|
||||
| 风险 | 止损/止盈点数 | **200** / **300** |
|
||||
| 风险 | 移动止损 | **true** |
|
||||
| 仓位 | 手数模式 | **结余风险比例** |
|
||||
| 仓位 | 固定手数 / 风险% | 0.01 / **5** |
|
||||
| 仓位 | 单笔最大手数 / 最大持仓笔数 | 99 / 99 |
|
||||
| 仓位 | 单方向最多一单 | **true** |
|
||||
| 保护 | 连亏冷冻次数 / 冷冻K线 | 3 / 60 |
|
||||
| 调试 | 调试信息 / 极值标记 / Magic / 禁手工单 | false / **true** / 20260516 / **true** |
|
||||
|
||||
## 参数设置
|
||||
|
||||
### 波段识别参数
|
||||
- K线周期:支持参数设置,不跟随图表,默认 **1 Minute**
|
||||
- MA周期:默认 **14**
|
||||
- **最小波段阈值百分比:默认 0.1%**(波段必须 >= 此值才可能是有效波段)
|
||||
- **最大波段阈值百分比:默认 1.0%**(波段必须 <= 此值才可能是有效波段)
|
||||
- **有效波段范围:0.1% - 1.0%**(只交易此范围内的波段)
|
||||
- **反向突破容忍度:默认0.0%(新功能 - 详见下方说明)**
|
||||
- **最小波段阈值百分比:默认 0.1%**
|
||||
- **最大波段阈值百分比:默认 10%**
|
||||
- **有效波段范围:0.1% - 10%**
|
||||
- **有效波段最少 K 线数:默认 3**
|
||||
- **反向突破容忍度:默认 0%**
|
||||
|
||||
### 风险管理参数
|
||||
- 止损百分比:默认 **0.05%**(基于开仓价计算,最小1美元/100点)
|
||||
- 盈亏比:默认 **1.5**(止盈 = 止损金额 × 盈亏比)
|
||||
- 使用移动止损:默认 **false**(禁用移动止损功能)
|
||||
- 最大持仓时间:默认 **5分钟**
|
||||
- 止损点数:默认 **200**
|
||||
- 止盈点数:默认 **300**
|
||||
- 使用移动止损:默认 **true**(浮盈达止损点数后移至成本价)
|
||||
|
||||
### 仓位管理参数
|
||||
- 使用复利模式:默认 **true**(启用复利模式)
|
||||
- 手数模式:默认 **结余风险比例 RiskPercent**(可选固定手数)
|
||||
- 固定手数:默认 **0.01手**
|
||||
- 每500$开仓手数:默认 **0.05手**(复利模式有效)
|
||||
- 最大开仓手数:默认 **99手**
|
||||
- 单方向最多持有一单:默认 **true**(每个方向最多持有1单)
|
||||
- 每笔风险占结余%:默认 **5%**
|
||||
- 单笔最大手数:默认 **99手**
|
||||
- 最大持仓笔数:默认 **99**
|
||||
- 单方向最多持有一单:默认 **true**
|
||||
|
||||
**RiskPercent 手数公式:**
|
||||
`风险金额 = 结余 × 风险%`;`手数 = 风险金额 ÷ (止损点数对应的每手亏损)`
|
||||
|
||||
### 连续亏损保护参数
|
||||
- 连续亏损次数触发冷冻:默认 **3**(设为0可禁用该功能)
|
||||
- 冷冻K线根数:默认 **60**(设为0可禁用该功能)
|
||||
- 连续亏损次数触发冷冻:默认 **3**
|
||||
- 冷冻K线根数:默认 **60**
|
||||
|
||||
### 手工单管理参数
|
||||
- 禁止手工单:默认 **true**(自动平掉所有手工单)
|
||||
### 调试选项
|
||||
- 显示调试信息:默认 **false**
|
||||
- 显示极值点标记:默认 **true**
|
||||
- EA魔术号:默认 **20260516**
|
||||
- 禁止手工单:默认 **true**
|
||||
|
||||
## 补充说明
|
||||
|
||||
@@ -59,6 +83,8 @@
|
||||
- 多头有效波段之间,仅允许出现一个多单突破K线和一个空单突破K线,反之亦然
|
||||
|
||||
### 有效波段规则
|
||||
- 相邻极值点价差须在最小~最大波段阈值范围内
|
||||
- 两极值点之间(含两端极值所在 K)**至少 3 根 K 线**,否则不构成有效波段
|
||||
- 一个有效波段只能开一单(无论是高点还是低点)
|
||||
- 开仓后该波段立即失效
|
||||
- 必须等新的有效波段形成才能再次开仓
|
||||
@@ -67,12 +93,8 @@
|
||||
此参数用于控制同一方向的持仓数量,避免单边风险过大。
|
||||
|
||||
**参数说明:**
|
||||
- **false(默认)**:允许同方向开多单,仅受"最大开仓手数"限制
|
||||
- 例如:最大开仓手数=3时,可以同时持有3个多单,或3个空单,或2多1空等
|
||||
- **true**:每个方向最多持有1单
|
||||
- 多单方向:最多持有1个多单
|
||||
- 空单方向:最多持有1个空单
|
||||
- 最多同时持有1多+1空=2单
|
||||
- **false**:允许同方向开多单,仅受「单笔最大手数」「最大持仓笔数」限制
|
||||
- **true(默认)**:每个方向最多持有 1 单(最多 1 多 + 1 空)
|
||||
|
||||
**使用场景:**
|
||||
- **保守型交易者**:建议设置为 **true**
|
||||
@@ -342,12 +364,12 @@
|
||||
#### 示例说明
|
||||
假设设置:
|
||||
- 最小阈值:0.1%
|
||||
- 最大阈值:1.0%
|
||||
- 最大阈值:10%
|
||||
- 当前价格:2600
|
||||
|
||||
**价格2600时的阈值范围:**
|
||||
- 最小阈值 = 2600 × 0.1% = 2.6美元(260点)
|
||||
- 最大阈值 = 2600 × 1.0% = 26美元(2600点)
|
||||
- 最大阈值 = 2600 × 10% = 260美元(26000点)
|
||||
|
||||
**波段筛选结果:**
|
||||
- 波段 2.0美元(200点):< 最小阈值 → ❌ 不交易(波段太小)
|
||||
@@ -357,44 +379,18 @@
|
||||
|
||||
**价格4700时的阈值范围:**
|
||||
- 最小阈值 = 4700 × 0.1% = 4.7美元(470点)
|
||||
- 最大阈值 = 4700 × 1.0% = 47美元(4700点)
|
||||
- 最大阈值 = 4700 × 10% = 470美元(47000点)
|
||||
|
||||
#### 参数调整建议
|
||||
- **保守型**:0.15% - 0.5%(只交易中小波段)
|
||||
- **标准型**:0.1% - 1.0%(默认,覆盖大多数波段)
|
||||
- **激进型**:0.08% - 2.0%(交易范围更广)
|
||||
- **保守型**:0.15% - 0.5%
|
||||
- **标准型**:0.1% - 10%(默认)
|
||||
- **激进型**:0.08% - 15%
|
||||
|
||||
### 止损止盈说明
|
||||
本策略使用**百分比模式**计算止损和止盈:
|
||||
- **止损**:开仓价(或挂单触发价)± **止损点数** × Point
|
||||
- **止盈**:开仓价(或挂单触发价)± **止盈点数** × Point
|
||||
- **移动止损**(可选):浮盈 ≥ 止损点数对应的价格距离时,将止损移至成本价(仅一次)
|
||||
|
||||
- **止损计算**:止损金额 = MAX(开仓价 × 止损百分比, 1美元)
|
||||
- 示例1(高价位):开仓价2600,止损0.05%,计算值 = 2600 × 0.05% = 1.3美元 > 1美元,使用1.3美元
|
||||
- 多单止损价 = 2600 - 1.3 = 2598.7
|
||||
- 空单止损价 = 2600 + 1.3 = 2601.3
|
||||
- 示例2(低价位):开仓价1500,止损0.05%,计算值 = 1500 × 0.05% = 0.75美元 < 1美元,使用最小值1美元
|
||||
- 多单止损价 = 1500 - 1 = 1499.0
|
||||
- 空单止损价 = 1500 + 1 = 1501.0
|
||||
|
||||
- **止盈计算**:止盈金额 = 止损金额 × 盈亏比
|
||||
- 示例1:止损金额1.3美元,盈亏比1.5,止盈金额 = 1.3 × 1.5 = 1.95美元
|
||||
- 多单止盈价 = 2600 + 1.95 = 2601.95
|
||||
- 空单止盈价 = 2600 - 1.95 = 2598.05
|
||||
- 示例2:止损金额1美元,盈亏比1.5,止盈金额 = 1 × 1.5 = 1.5美元
|
||||
- 多单止盈价 = 1500 + 1.5 = 1501.5
|
||||
- 空单止盈价 = 1500 - 1.5 = 1498.5
|
||||
|
||||
- **移动止损**(可选功能,默认启用):
|
||||
- **触发条件**:当浮盈 >= 止损金额时触发
|
||||
- **移动目标**:将止损移至成本价(开仓价)
|
||||
- **移动次数**:仅移动一次(保本策略)
|
||||
- **示例**:开仓价2603.24,止损金额1.30美元
|
||||
- 当价格涨至2604.54(浮盈1.30)时,止损从2601.94移至2603.24
|
||||
- 之后价格无论涨到多高,止损都保持在2603.24
|
||||
- 即使回调至成本价,也能保本离场,不会亏损
|
||||
- **开关控制**:可通过参数"使用移动止损"关闭此功能
|
||||
|
||||
- **最小止损保护**:无论百分比设置多小,止损金额始终不低于1美元(100点),避免止损过小导致频繁触发
|
||||
|
||||
- **移动止损的作用**:
|
||||
- ✅ 启用时:浮盈达标后锁定盈亏平衡,避免盈利单变亏损
|
||||
- ❌ 禁用时:止损始终保持在初始位置,追求更高止盈,但风险更大
|
||||
示例(多单,触发价 2610,止损 200 点、止盈 300 点):
|
||||
- 止损价 ≈ 2610 − 2.00 = **2608.00**(黄金 2 位小数时按品种 Point 换算)
|
||||
- 止盈价 ≈ 2610 + 3.00 = **2613.00**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,400 +0,0 @@
|
||||
# 突破交易策略
|
||||
|
||||
## 基础概念
|
||||
- 平台:MQL5
|
||||
- 品种:XAU黄金
|
||||
- 1美金:4100与4101之间相差1美金
|
||||
- 1美金 = 100点
|
||||
|
||||
## 辅助指标
|
||||
- 移动平均线(Moving Average)
|
||||
- 周期默认设置为:14
|
||||
|
||||
## 核心概念
|
||||
- 突破K线:开盘价在均线下方,收盘价在均线上方,称之为多单突破K线,反之亦然
|
||||
- 极值点:相邻两根突破K线之间所有K线(包括突破K线)中的最高价或最低价称之为极值点
|
||||
- 有效波段:相邻两个极值点的价差达到波段阈值点数时,期间的所有K线(包含突破K线本身)组成的行情,称之为有效波段
|
||||
|
||||
## 开仓逻辑
|
||||
- 价格突破有效波段的高点开多单,价格突破有效波点的低点开空单
|
||||
- 该开仓逻辑仅适用最新的一个有效波段
|
||||
- 止损和止盈基于开仓价的百分比计算,止盈 = 止损金额 × 盈亏比
|
||||
- 浮盈达到止损金额后,即可将止损移动至成本价
|
||||
- 已经开仓后,继续监控新的有效波段突破,每单开仓后就与有效波段无关了
|
||||
|
||||
## 参数设置
|
||||
|
||||
### 波段识别参数
|
||||
- K线周期:支持参数设置,不跟随图表,默认 **1 Minute**
|
||||
- MA周期:默认 **14**
|
||||
- **最小波段阈值百分比:默认 0.1%**(波段必须 >= 此值才可能是有效波段)
|
||||
- **最大波段阈值百分比:默认 1.0%**(波段必须 <= 此值才可能是有效波段)
|
||||
- **有效波段范围:0.1% - 1.0%**(只交易此范围内的波段)
|
||||
- **反向突破容忍度:默认0.0%(新功能 - 详见下方说明)**
|
||||
|
||||
### 风险管理参数
|
||||
- 止损百分比:默认 **0.05%**(基于开仓价计算,最小1美元/100点)
|
||||
- 盈亏比:默认 **1.5**(止盈 = 止损金额 × 盈亏比)
|
||||
- 使用移动止损:默认 **false**(禁用移动止损功能)
|
||||
- 最大持仓时间:默认 **5分钟**
|
||||
|
||||
### 仓位管理参数
|
||||
- 使用复利模式:默认 **true**(启用复利模式)
|
||||
- 固定手数:默认 **0.01手**
|
||||
- 每500$开仓手数:默认 **0.05手**(复利模式有效)
|
||||
- 最大开仓手数:默认 **99手**
|
||||
- 单方向最多持有一单:默认 **true**(每个方向最多持有1单)
|
||||
|
||||
### 连续亏损保护参数
|
||||
- 连续亏损次数触发冷冻:默认 **3**(设为0可禁用该功能)
|
||||
- 冷冻K线根数:默认 **60**(设为0可禁用该功能)
|
||||
|
||||
### 手工单管理参数
|
||||
- 禁止手工单:默认 **true**(自动平掉所有手工单)
|
||||
|
||||
## 补充说明
|
||||
|
||||
### 突破K线处理
|
||||
- 如果出现多个多单突破K线,仅保留最低价更低的那个K线,反之亦然
|
||||
- 多头有效波段之间,仅允许出现一个多单突破K线和一个空单突破K线,反之亦然
|
||||
|
||||
### 有效波段规则
|
||||
- 一个有效波段只能开一单(无论是高点还是低点)
|
||||
- 开仓后该波段立即失效
|
||||
- 必须等新的有效波段形成才能再次开仓
|
||||
|
||||
### 单方向持仓限制
|
||||
此参数用于控制同一方向的持仓数量,避免单边风险过大。
|
||||
|
||||
**参数说明:**
|
||||
- **false(默认)**:允许同方向开多单,仅受"最大开仓手数"限制
|
||||
- 例如:最大开仓手数=3时,可以同时持有3个多单,或3个空单,或2多1空等
|
||||
- **true**:每个方向最多持有1单
|
||||
- 多单方向:最多持有1个多单
|
||||
- 空单方向:最多持有1个空单
|
||||
- 最多同时持有1多+1空=2单
|
||||
|
||||
**使用场景:**
|
||||
- **保守型交易者**:建议设置为 **true**
|
||||
- 严格控制风险敞口
|
||||
- 避免单边持仓过重
|
||||
- 适合波动较大的行情
|
||||
|
||||
- **激进型交易者**:可设置为 **false**
|
||||
- 允许同方向加仓
|
||||
- 在强趋势行情中获取更多盈利
|
||||
- 但需要配合"最大开仓手数"参数控制总风险
|
||||
|
||||
**示例:**
|
||||
```
|
||||
设置:单方向最多持有一单 = true
|
||||
|
||||
场景1:当前持有1个多单
|
||||
- 新的多单信号出现 → ❌ 被忽略(已有多单)
|
||||
- 新的空单信号出现 → ✅ 可以开仓(空单方向无持仓)
|
||||
|
||||
场景2:当前持有1个多单 + 1个空单
|
||||
- 新的多单信号出现 → ❌ 被忽略(已有多单)
|
||||
- 新的空单信号出现 → ❌ 被忽略(已有空单)
|
||||
```
|
||||
|
||||
### 连续亏损保护功能 ⭐
|
||||
|
||||
此功能用于在连续亏损后自动暂停交易,避免在不利行情中持续亏损。
|
||||
|
||||
**核心机制:**
|
||||
- 实时监控每笔交易的盈亏结果(通过 `OnTradeTransaction` 事件)
|
||||
- 当连续亏损达到设定次数时,自动进入"冷冻期"
|
||||
- 冷冻期内禁止开新仓,直到指定数量的K线走完
|
||||
- 冷冻期结束后自动解除,连续亏损计数器重置为0
|
||||
|
||||
**参数说明:**
|
||||
|
||||
1. **连续亏损次数触发冷冻**:默认 `3`
|
||||
- 设置为 `0`:功能完全禁用
|
||||
- 设置为 `3`:连续亏损3笔后触发冷冻(推荐值)
|
||||
- 任何盈利单都会将计数器归零
|
||||
|
||||
2. **冷冻K线根数**:默认 `60`
|
||||
- 设置为 `0`:功能完全禁用
|
||||
- 设置为 `60`:冷冻期间禁止开仓,直到60根K线走完(M1周期约1小时)
|
||||
- K线周期取决于"K线周期"参数设置
|
||||
|
||||
**工作流程:**
|
||||
|
||||
```
|
||||
步骤1:监控交易结果
|
||||
- 订单平仓后,检测盈亏(包含手续费和隔夜利息)
|
||||
- 亏损 → 连续亏损计数器 +1
|
||||
- 盈利 → 连续亏损计数器归零
|
||||
|
||||
步骤2:触发冷冻条件
|
||||
- 当连续亏损计数器 >= 设定次数时
|
||||
- 记录当前K线数量
|
||||
- 计算冷冻结束K线数 = 当前K线数 + 冷冻K线根数
|
||||
- 打印冷冻提示信息
|
||||
|
||||
步骤3:冷冻期检查
|
||||
- 每次开仓前检查是否处于冷冻期
|
||||
- 如果当前K线数 < 冷冻结束K线数 → 拒绝开仓
|
||||
- 如果当前K线数 >= 冷冻结束K线数 → 解除冷冻,重置计数器
|
||||
|
||||
步骤4:冷冻解除
|
||||
- 自动打印解除提示
|
||||
- 连续亏损计数器重置为0
|
||||
- 恢复正常交易
|
||||
```
|
||||
|
||||
**使用场景:**
|
||||
|
||||
1. **震荡行情保护**(默认配置)
|
||||
- 设置:连续亏损3次 + 冷冻60根K线
|
||||
- 效果:在频繁假突破的震荡行情中,连续亏损3次后暂停1小时(M1周期)
|
||||
- 避免在不利行情中持续开仓亏损
|
||||
|
||||
2. **回测优化**
|
||||
- 通过历史数据测试不同参数组合
|
||||
- 找到最佳的连续亏损阈值和冷冻时长
|
||||
- 提高策略整体盈利能力
|
||||
|
||||
3. **风险控制**
|
||||
- 配合"最大开仓手数"和"单方向持仓限制"
|
||||
- 多层次控制交易风险
|
||||
- 保守型交易者建议启用
|
||||
|
||||
**示例:**
|
||||
|
||||
```
|
||||
参数设置:
|
||||
- 连续亏损次数触发冷冻 = 3
|
||||
- 冷冻K线根数 = 10
|
||||
- K线周期 = M1
|
||||
|
||||
交易流程:
|
||||
时间 09:00 → 开仓多单 → 止损平仓(亏损)→ 连续亏损计数 = 1
|
||||
时间 09:05 → 开仓空单 → 止损平仓(亏损)→ 连续亏损计数 = 2
|
||||
时间 09:10 → 开仓多单 → 止损平仓(亏损)→ 连续亏损计数 = 3
|
||||
|
||||
触发冷冻!
|
||||
- 当前K线数:150
|
||||
- 冷冻至K线数:160(150 + 10)
|
||||
- 打印:=== 触发交易冷冻 ===
|
||||
|
||||
冷冻期内(09:10 - 09:20):
|
||||
- 所有开仓信号被忽略
|
||||
- 调试模式下打印:交易冷冻中 - 剩余K线数: X
|
||||
|
||||
时间 09:20(K线数达到160):
|
||||
- 自动解除冷冻
|
||||
- 打印:=== 交易冷冻解除 ===
|
||||
- 连续亏损计数器重置为0
|
||||
- 恢复正常交易
|
||||
```
|
||||
|
||||
**注意事项:**
|
||||
|
||||
1. **功能启用条件**
|
||||
- 两个参数都必须 > 0 才会启用
|
||||
- 任一参数为 0,功能完全禁用
|
||||
|
||||
2. **计数逻辑**
|
||||
- 仅统计"净亏损"(盈亏 + 手续费 + 隔夜利息)
|
||||
- 盈亏持平(净盈利约等于0)不会重置计数器
|
||||
- 任何盈利单(净盈利 > 0)立即重置计数器
|
||||
|
||||
3. **冷冻期计算**
|
||||
- 基于K线根数,不是绝对时间
|
||||
- 周末或节假日市场休市时,K线不增加,冷冻期相应延长
|
||||
- 建议根据具体交易品种和周期调整参数
|
||||
|
||||
4. **与其他功能的关系**
|
||||
- 冷冻期内不影响已有持仓的管理(止损、止盈、移动止损仍正常工作)
|
||||
- 仅阻止新开仓,不会强制平仓现有持仓
|
||||
- 可配合"单方向持仓限制"等功能使用
|
||||
|
||||
### 禁止手工单功能说明 ⭐
|
||||
|
||||
**问题背景:**
|
||||
在EA自动交易过程中,手工开单可能会干扰EA的整体策略逻辑和风险管理,导致:
|
||||
- 破坏EA的仓位管理计划
|
||||
- 影响资金管理和风险控制
|
||||
- 与EA策略产生冲突
|
||||
|
||||
**功能说明:**
|
||||
- 启用后,EA会自动检测并平掉所有手工单(magic number = 0的订单)
|
||||
- 仅处理当前品种(_Symbol)的手工单
|
||||
- 平仓后会在日志中打印详细信息
|
||||
|
||||
**识别原理:**
|
||||
在MQL5中,订单都有一个magic number(魔术号)属性:
|
||||
- **手工单**:magic number = 0(默认值)
|
||||
- **EA开单**:magic number = EA设定的值(本策略默认20260516)
|
||||
|
||||
**参数说明:**
|
||||
- **true(默认)**:启用保护,自动平掉手工单
|
||||
- 每个Tick检查一次
|
||||
- 发现手工单立即平仓
|
||||
- 打印平仓日志:`【禁止手工单】已平掉手工单 - Ticket:xxx 类型:多单/空单`
|
||||
|
||||
- **false**:允许手工单与EA单共存
|
||||
- 手工单和EA单可以同时存在
|
||||
- 手工单不会被EA管理(不会移动止损等)
|
||||
- 适合需要手工干预的场景
|
||||
|
||||
**使用场景:**
|
||||
|
||||
1. **完全自动化交易**(推荐设置:true)
|
||||
- 避免误操作影响EA策略
|
||||
- 确保严格执行EA逻辑
|
||||
- 防止情绪化交易
|
||||
|
||||
2. **半自动化交易**(设置:false)
|
||||
- 允许手工单和EA单并存
|
||||
- 可以手工补仓或对冲
|
||||
- 需要手动管理手工单的风险
|
||||
|
||||
**示例:**
|
||||
|
||||
```
|
||||
参数设置:禁止手工单 = true
|
||||
|
||||
情况1:用户在MT5手动开了1个多单
|
||||
- EA在下一个Tick检测到该订单的magic=0
|
||||
- 自动平掉该订单
|
||||
- 打印:【禁止手工单】已平掉手工单 - Ticket:12345 类型:多单
|
||||
|
||||
情况2:EA自动开了1个多单(magic=20260516)
|
||||
- EA检测到magic != 0,确认是EA自己开的单
|
||||
- 不会平仓,正常管理(移动止损等)
|
||||
|
||||
情况3:其他EA开的单(magic=99999)
|
||||
- EA检测到magic != 0,确认不是手工单
|
||||
- 不会平仓,但也不会管理
|
||||
```
|
||||
|
||||
**注意事项:**
|
||||
|
||||
1. **仅处理当前品种**
|
||||
- 只平掉当前EA运行品种的手工单
|
||||
- 不会影响其他品种的手工单
|
||||
|
||||
2. **实时检测**
|
||||
- 每个Tick都会检查
|
||||
- 手工单会在极短时间内被平掉
|
||||
|
||||
3. **与其他功能的关系**
|
||||
- 不影响EA自身的开仓逻辑
|
||||
- 不影响EA的风险管理功能
|
||||
- 可配合所有其他参数使用
|
||||
|
||||
4. **平仓失败处理**
|
||||
- 如果平仓失败(网络问题等),会打印错误日志
|
||||
- 下一个Tick会继续尝试平仓
|
||||
|
||||
### 反向突破容忍度(新功能)⭐
|
||||
|
||||
**问题背景:**
|
||||
在趋势行情中,可能会出现反向突破K线(回调),导致原本完整的波段被"切断",使策略无法识别出肉眼可见的有效波段。
|
||||
|
||||
**示例:**
|
||||
```
|
||||
价格从2600上涨到2650(涨幅50点)
|
||||
中间出现一根空单突破K线(回调到2640)
|
||||
|
||||
原有逻辑:
|
||||
- 波段被切断为:2600→2640 和 2640→2650
|
||||
- 每段都不足1000点,无法识别为有效波段 ✗
|
||||
|
||||
容忍模式(设置30%):
|
||||
- 回调10点 ÷ 波段50点 = 20% < 30%
|
||||
- 回撤在容忍范围内,继续计算完整波段
|
||||
- 识别出2600→2650的有效波段 ✓
|
||||
```
|
||||
|
||||
**参数说明:**
|
||||
- **0%(默认)**:不容忍任何反向突破,保持原有逻辑
|
||||
- **10-20%**:容忍小幅回调,适合短期趋势
|
||||
- **30-40%**:容忍中等回调,适合日内波段
|
||||
- **50%以上**:容忍大幅回调,可能识别出更大的波段(但也可能误判)
|
||||
|
||||
**推荐设置:**
|
||||
- 保守型:0-10%
|
||||
- 标准型:20-30%
|
||||
- 激进型:30-50%
|
||||
|
||||
**注意事项:**
|
||||
1. 设置为0时,完全保持原有逻辑,不影响现有回测结果
|
||||
2. 容忍度越高,识别的波段越大,但也可能包含更多噪音
|
||||
3. 建议先回测不同容忍度,找到最适合的数值
|
||||
4. 容忍度只影响波段识别,不影响止损止盈等其他逻辑
|
||||
|
||||
### 波段阈值范围说明
|
||||
本策略使用**波段范围筛选**机制,只交易特定大小的波段:
|
||||
|
||||
#### 为什么需要波段范围?
|
||||
- **单一阈值的问题**:传统策略只设置最小阈值(如0.1%),导致0.1%的小波段和5%的大波段都交易,风险收益特征差异巨大
|
||||
- **范围筛选的优势**:通过设置最小和最大阈值,只交易特定大小的波段,风险更可控
|
||||
|
||||
#### 计算方式
|
||||
- 以前一个极值点价格为基准,按百分比计算阈值范围
|
||||
- 动态适应不同价格水平
|
||||
|
||||
#### 示例说明
|
||||
假设设置:
|
||||
- 最小阈值:0.1%
|
||||
- 最大阈值:1.0%
|
||||
- 当前价格:2600
|
||||
|
||||
**价格2600时的阈值范围:**
|
||||
- 最小阈值 = 2600 × 0.1% = 2.6美元(260点)
|
||||
- 最大阈值 = 2600 × 1.0% = 26美元(2600点)
|
||||
|
||||
**波段筛选结果:**
|
||||
- 波段 2.0美元(200点):< 最小阈值 → ❌ 不交易(波段太小)
|
||||
- 波段 5.2美元(520点):在范围内 → ✅ 有效波段,可交易
|
||||
- 波段 15美元(1500点):在范围内 → ✅ 有效波段,可交易
|
||||
- 波段 30美元(3000点):> 最大阈值 → ❌ 不交易(波段太大)
|
||||
|
||||
**价格4700时的阈值范围:**
|
||||
- 最小阈值 = 4700 × 0.1% = 4.7美元(470点)
|
||||
- 最大阈值 = 4700 × 1.0% = 47美元(4700点)
|
||||
|
||||
#### 参数调整建议
|
||||
- **保守型**:0.15% - 0.5%(只交易中小波段)
|
||||
- **标准型**:0.1% - 1.0%(默认,覆盖大多数波段)
|
||||
- **激进型**:0.08% - 2.0%(交易范围更广)
|
||||
|
||||
### 止损止盈说明
|
||||
本策略使用**百分比模式**计算止损和止盈:
|
||||
|
||||
- **止损计算**:止损金额 = MAX(开仓价 × 止损百分比, 1美元)
|
||||
- 示例1(高价位):开仓价2600,止损0.05%,计算值 = 2600 × 0.05% = 1.3美元 > 1美元,使用1.3美元
|
||||
- 多单止损价 = 2600 - 1.3 = 2598.7
|
||||
- 空单止损价 = 2600 + 1.3 = 2601.3
|
||||
- 示例2(低价位):开仓价1500,止损0.05%,计算值 = 1500 × 0.05% = 0.75美元 < 1美元,使用最小值1美元
|
||||
- 多单止损价 = 1500 - 1 = 1499.0
|
||||
- 空单止损价 = 1500 + 1 = 1501.0
|
||||
|
||||
- **止盈计算**:止盈金额 = 止损金额 × 盈亏比
|
||||
- 示例1:止损金额1.3美元,盈亏比1.5,止盈金额 = 1.3 × 1.5 = 1.95美元
|
||||
- 多单止盈价 = 2600 + 1.95 = 2601.95
|
||||
- 空单止盈价 = 2600 - 1.95 = 2598.05
|
||||
- 示例2:止损金额1美元,盈亏比1.5,止盈金额 = 1 × 1.5 = 1.5美元
|
||||
- 多单止盈价 = 1500 + 1.5 = 1501.5
|
||||
- 空单止盈价 = 1500 - 1.5 = 1498.5
|
||||
|
||||
- **移动止损**(可选功能,默认启用):
|
||||
- **触发条件**:当浮盈 >= 止损金额时触发
|
||||
- **移动目标**:将止损移至成本价(开仓价)
|
||||
- **移动次数**:仅移动一次(保本策略)
|
||||
- **示例**:开仓价2603.24,止损金额1.30美元
|
||||
- 当价格涨至2604.54(浮盈1.30)时,止损从2601.94移至2603.24
|
||||
- 之后价格无论涨到多高,止损都保持在2603.24
|
||||
- 即使回调至成本价,也能保本离场,不会亏损
|
||||
- **开关控制**:可通过参数"使用移动止损"关闭此功能
|
||||
|
||||
- **最小止损保护**:无论百分比设置多小,止损金额始终不低于1美元(100点),避免止损过小导致频繁触发
|
||||
|
||||
- **移动止损的作用**:
|
||||
- ✅ 启用时:浮盈达标后锁定盈亏平衡,避免盈利单变亏损
|
||||
- ❌ 禁用时:止损始终保持在初始位置,追求更高止盈,但风险更大
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
# 突破策略(极值点突破开仓)
|
||||
|
||||
MQL5 EA:`20260522_Breakout.mq5`(v3.18)。
|
||||
|
||||
## 参数默认值(与输入界面一致)
|
||||
|
||||
| 分组 | 参数 | 默认 |
|
||||
|------|------|------|
|
||||
| 波段 | K线周期 / MA周期 | M1 / 14 |
|
||||
| 波段 | 最小/最大波段% | 0.1 / **0.25** |
|
||||
| 波段 | 有效波段最少K线 | **3** |
|
||||
| 波段 | 反向突破容忍度% | **0.05** |
|
||||
| 交易 | 开仓方向 | **随机**(可选:顺势 / 反向 / 前日收线相对日MA / **日MA偏离率反向**) |
|
||||
| 交易 | 日MA偏离率阈值% | **1.0**(\|偏离\|超过才触发,0=关) |
|
||||
| 交易 | 按批统盈点数 / 止损% | **300** / **30** |
|
||||
| 交易 | 大单止盈阈值 / 点数 | **1.0手** / **500点** |
|
||||
| 交易 | 回落反向 / 监测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 档)
|
||||
|
||||
## 前日收线相对日MA(开仓方向选项)
|
||||
|
||||
取**前一交易日**日 K **收盘价**与**日 K 上同周期 MA**(默认 MA14)比较:
|
||||
|
||||
| 前日收线位置 | 允许开仓 |
|
||||
|-------------|----------|
|
||||
| **在日 MA 下方** | 破**低**极值 → 空;**高**极值出现回落反向 → 空 |
|
||||
| **在日 MA 上方** | 破**高**极值 → 多;**低**极值出现回落反向 → 多 |
|
||||
|
||||
- 偏空时不做「破高」突破单,等高极值**回落反向**或等破低;偏多时不做「破低」突破单,等低极值**回落反向**或等破高。
|
||||
- 前日收线恰在 MA 上(中性)时,该模式不开仓。
|
||||
|
||||
## 日 MA 偏离率反向(开仓方向选项)
|
||||
|
||||
**偏离率** = (当前价 − 日 MA) ÷ 日 MA × 100%(当前价取买卖中间价,日 MA 周期同 `InpMAPeriod`)。
|
||||
|
||||
| 偏离率 | 含义 | 允许开仓(反向/均值回归) |
|
||||
|--------|------|--------------------------|
|
||||
| **> +阈值%** | 价显著高于日 MA | 破**高** → 空;**高**极值回落反向 → 空 |
|
||||
| **< −阈值%** | 价显著低于日 MA | 破**低** → 多;**低**极值回落反向 → 多 |
|
||||
| \|偏离\| ≤ 阈值 | 未超阈值 | **不开仓** |
|
||||
|
||||
参数 `日MA偏离率阈值` 默认 **1.0%**,设为 **0** 关闭此逻辑。
|
||||
|
||||
## 大单独立止盈
|
||||
|
||||
- 开仓手数 **≥ 大单止盈阈值**(默认 1 手)的单子,按 **大单最大止盈点数** 单独止盈,**不参与按批统盈**的加权均价计算。
|
||||
- 大单止盈平仓后(平台 TP 或 EA 监测触发),**下一档马丁手数**与**统盈均价**均仅按**当前仍持仓**的单子重算,与已平大单无关。
|
||||
- 阈值或点数设为 **0** 时关闭此功能,所有单子统一走按批统盈。
|
||||
|
||||
## 说明
|
||||
|
||||
- 突破开仓与回落反向共用档位手数;列表长度 = 同向最多开仓笔数。
|
||||
- 按批统盈、结余%止损、双向满档逻辑见 EA 输入注释。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
@@ -1,81 +0,0 @@
|
||||
EXNESS平台自动反用到帐时间:平仓后的第二天晚上十二点之前会自动反 用到交易帐号里面,请注意查收!
|
||||
|
||||
|
||||
2026年EXNESS自动日反开户链接:(可开代理)
|
||||
|
||||
https://one.exnessonelink.com/intl/zh/a/xiao2
|
||||
|
||||
合作伙伴代码:xiao2
|
||||
|
||||
EXNESS的后台又升级了,用这个链接开出来的帐号不需要人工上报申请日反了,是平台直接自动日反到帐号。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
2026年以前老代理名下客户注册链接:
|
||||
|
||||
https://www.extraders.market/pa/a/10795827
|
||||
|
||||
https://www.extraders.direct/pa/a/10795827
|
||||
|
||||
https://www.extraders.direct/a/10795827
|
||||
|
||||
https://www.extraders.asia/a/10795827
|
||||
|
||||
https://www.ex-markets.guide/a/10795827
|
||||
|
||||
https://www.ex-markets.pro/a/10795827
|
||||
|
||||
https://www.extrading.pro/a/10795827
|
||||
|
||||
https://www.ex-markets.direct/a/10795827
|
||||
|
||||
https://www.ex-markets.digital/a/10795827
|
||||
|
||||
https://one.exness-track.com/a/10795827
|
||||
|
||||
https://www.extrading.markets/a/10795827
|
||||
|
||||
https://www.ex-markets.expert/a/10795827
|
||||
|
||||
https://one.exnesstrack.org/boarding/sign-up/a/10795827
|
||||
|
||||
注册时请填写合作伙伴代码:10795827
|
||||
|
||||
一定要记得打开链接之后第一时间去注册,这个很重要
|
||||
|
||||
|
||||
EXNESS开户需要:身份证正面和反面照片,邮箱,手机号
|
||||
|
||||
打开链接后,请直接点击:开立帐户
|
||||
|
||||
关于内转:
|
||||
|
||||
第一是,平台不允许我们这样做
|
||||
|
||||
第二是,我们也被骗过
|
||||
|
||||
第三,为了客户的资金安全,我们也不会和任何客户发现任何金钱往来
|
||||
|
||||
注意:谁先付款都有隐患,因为没有第三方担保。所以我们一般都会建议客户直接通过平台进行出入米,客户和平台之间有金钱往来,出了问题,平台还可以去查,去处理。
|
||||
|
||||
|
||||
|
||||
EXNESS代理:
|
||||
1.您和您下面客户开户,都需要找我拿开户链接(只能用我们的开户链接开户)
|
||||
|
||||
2.您客户开完户之后,需要把资料发给我查询是否在我们代理下,同步把客户的交易帐号绑定到您的代理返佣后台(请在第一时间去绑定,以免影响到您的返佣)
|
||||
|
||||
3.您本人的交易帐号是平台自动日返到交易帐号,您下面客户的返佣是周返到您的指定交易帐号(需绑定代理返佣后台)
|
||||
|
||||
注:开户之后请第一时间发帐号给我核查代理归属,并上报平台反用,谢谢
|
||||
|
||||
|
||||
关于EXNESS平台情况说明:身为代理,我们只提供基础服务:链接和返佣
|
||||
|
||||
任何平台方面的问题(入金,出金,品种,交易,服务器等等),请客户自己联系平台客服处理,因为平台现在提供的服务人员只有平台客服,没有其它,全球所有的客户都是一样的,只能自己联系平台客服去处理问题,我们这么大一个代理,有事儿找平台也是联系平台客服进行处理!
|
||||
|
||||
(不要说什么我是在你这儿开的户,出了问题,你就要处理的这种话,我们只是代理,但是代表不了平台。我们重来没有强迫任何客户去开户,开户是你自己自愿开的,你不开也没人强迫你开,交易是你自己操作的,我们重来不会干预客户的任何交易,不闻,不问,不管,不干预!你来找我要链接,我就给,你来找我处理反用,我就上报,没有其它更多的了)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,804 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 20260425.ea.mq5 |
|
||||
//| 马丁网格:小周期震荡、加仓后整体盈利平仓;K 线周期可独立于图表 |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "EA-2026"
|
||||
#property link ""
|
||||
#property version "1.11"
|
||||
#property description "Martin grid: TP/统盈/加仓间距=%% of ref price; max orders/side; no K-timeout close (v1.11+)"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include <Trade\SymbolInfo.mqh>
|
||||
#include <Trade\PositionInfo.mqh>
|
||||
|
||||
CTrade g_trade;
|
||||
CSymbolInfo g_sym;
|
||||
CPositionInfo g_pos;
|
||||
|
||||
// 内置 20 档(解析 InpLotLadder 失败或节数不足时回退) — 与当前默认字符串一致
|
||||
const double g_builtinLadder[20] =
|
||||
{
|
||||
0.05, 0.05, 0.05, 0.10, 0.10, 0.10, 0.15, 0.20, 0.20, 0.25,
|
||||
0.30, 0.35, 0.45, 0.55, 0.65, 0.75, 0.90, 1.2, 1.4, 1.5
|
||||
};
|
||||
double g_lotLadder[20];
|
||||
#define MGRID_VTP_B "MGvTP_20260425_BUY"
|
||||
#define MGRID_VTP_S "MGvTP_20260425_SELL"
|
||||
#define MGRID_TGT_L "MG_20260425_TGTLIST"
|
||||
|
||||
//--- 以下三处为「相对**参考价**的价距%」, 在运行时按当刻价格换算为实际价格步长(非点)
|
||||
input group "滑点"
|
||||
input int InpSlippage = 30; // 滑点(点)
|
||||
|
||||
input group "识别"
|
||||
input long InpMagic = 20260425; // Magics
|
||||
|
||||
input group "首单与统盈(百分比,见底部说明)"
|
||||
input double InpFirstTpPercent = 0.05; // 首档(仅1~Lot1层)止盈:每单= **该笔开仓价**×% 为价距;>=2层用统盈% ;<=0=不挂首档TP(单档时改按统盈%平)
|
||||
input double InpAveTpPercent = 0.05; // 统盈:相对**整体成本价** 的价距%(>=2 档整篮; 1档且未挂首档TP时亦用)
|
||||
|
||||
input group "加仓"
|
||||
input double InpDisPercent = 0.1; // 与上一同向**开仓价** 的间距:价距= lastOpen×%(市价多=Ask,空=Bid 与 last 比)
|
||||
input double InpAddTimes = 1.2; // 满 20 单后再加仓:在 Lot20 上乘此倍率 (Add_times)
|
||||
input int InpMaxOrdersPerSide = 20; // 单方向最多同时持仓**笔数**;达到后不再开新仓(不平仓,可手工);0=不限制
|
||||
|
||||
input group "头寸(第1~20档,一行逗号分隔)"
|
||||
input string InpLotLadder = "0.05,0.05,0.05,0.10,0.10,0.10,0.15,0.20,0.20,0.25,0.30,0.35,0.45,0.55,0.65,0.75,0.90,1.2,1.4,1.5";
|
||||
|
||||
input group "拆单"
|
||||
input int InpOrdersPerAdd = 1; // 每次加仓/首单 同时下几笔同向单(>=1,总手数仍为一档合计)
|
||||
|
||||
input group "其它"
|
||||
input int InpMaxSpreadPoints = 0; // 最大点差(点),0=不限制
|
||||
input bool InpLog = true; // 专家日志
|
||||
|
||||
input group "图表(统盈参考线)"
|
||||
input bool InpDrawVtp = true; // 多/空各一条虚拟水平线(仅参考,非真实挂单)
|
||||
input int InpVtpLineWidth = 1;
|
||||
input color InpVtpColorLong = clrDodgerBlue;
|
||||
input color InpVtpColorShort = clrDarkOrange;
|
||||
input bool InpShowBasketTgtList = true; // 图上列表:每单#、开仓、统盈目标价(与程序整篮价一致,非订单TP栏)
|
||||
input color InpTgtListColor = clrNavy;
|
||||
input int InpTgtListFont = 9;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
double PointValue()
|
||||
{
|
||||
if(!g_sym.Name(_Symbol))
|
||||
return _Point;
|
||||
g_sym.RefreshRates();
|
||||
return g_sym.Point();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
// 价距 = 参考价 × (参数百分比/100);参考为各用途各自给定的开仓/成本(见输入说明)
|
||||
//+------------------------------------------------------------------+
|
||||
double PctOfPrice(const double refPrice, const double pct)
|
||||
{
|
||||
if(refPrice <= 0.0)
|
||||
return 0.0;
|
||||
if(pct <= 0.0)
|
||||
return 0.0;
|
||||
return refPrice * (pct / 100.0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
double MinLot() { return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); }
|
||||
double MaxLot() { return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); }
|
||||
double LotStep(){ return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); }
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
string TrimToken(const string u)
|
||||
{
|
||||
string t = u;
|
||||
const int L0 = (int)StringLen(t);
|
||||
for(int a = 0; a < L0; a++)
|
||||
{
|
||||
if(StringGetCharacter(t, 0) != 32)
|
||||
break;
|
||||
t = StringSubstr(t, 1);
|
||||
}
|
||||
int L = (int)StringLen(t);
|
||||
for(int b = 0; b < L; b++)
|
||||
{
|
||||
if(StringGetCharacter(t, L - 1) != 32)
|
||||
break;
|
||||
t = StringSubstr(t, 0, L - 1);
|
||||
L = (int)StringLen(t);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
// 自 InpLotLadder 解析 20 档; 节数/数值不对则回退 g_builtinLadder
|
||||
//+------------------------------------------------------------------+
|
||||
void InitLotLadder()
|
||||
{
|
||||
for(int a = 0; a < 20; a++)
|
||||
g_lotLadder[a] = g_builtinLadder[a];
|
||||
const int slen = (int)StringLen(InpLotLadder);
|
||||
if(slen < 1)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("InpLotLadder empty, using builtin 20-lot");
|
||||
return;
|
||||
}
|
||||
string toks[];
|
||||
// StringSplit(…,ushort,[]) 无隐式 string→ushort 警告
|
||||
int n = StringSplit(InpLotLadder, (ushort)44, toks);
|
||||
if(n < 20)
|
||||
{
|
||||
n = StringSplit(InpLotLadder, (ushort)59, toks);
|
||||
}
|
||||
if(n < 20)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("InpLotLadder need 20 numbers, got ", n, " — builtin");
|
||||
return;
|
||||
}
|
||||
for(int k = 0; k < 20; k++)
|
||||
{
|
||||
const double w = StringToDouble(TrimToken(toks[k]));
|
||||
if(w <= 0.0)
|
||||
{
|
||||
g_lotLadder[k] = g_builtinLadder[k];
|
||||
}
|
||||
else
|
||||
g_lotLadder[k] = w;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void LogInitDiagnostics()
|
||||
{
|
||||
if(!InpLog)
|
||||
return;
|
||||
Print("20260425 v1.11 ", _Symbol, " magic=", (long)InpMagic,
|
||||
" 首档TP%=", InpFirstTpPercent, " 统盈%=", InpAveTpPercent, " 加仓距%=", InpDisPercent,
|
||||
" 单向最多笔数=", InpMaxOrdersPerSide);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool SpreadOk()
|
||||
{
|
||||
if(InpMaxSpreadPoints <= 0)
|
||||
return true;
|
||||
const int sp = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
||||
if(sp > InpMaxSpreadPoints)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
// 0-based 第 idx 档同向总手数(第0档=第1单 … 第19档=第20单; 第20档起为 第20档手数*倍率^n)
|
||||
//+------------------------------------------------------------------+
|
||||
double LotForLayerIndex(const int idx)
|
||||
{
|
||||
const double m = InpAddTimes;
|
||||
if(idx < 0)
|
||||
return MinLot();
|
||||
if(idx < 20)
|
||||
return g_lotLadder[idx];
|
||||
// idx >= 20: 以第20档(索引19)为基准
|
||||
const int p = idx - 19;
|
||||
return g_lotLadder[19] * MathPow(m, p);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
double NormalizeVolume(const double v)
|
||||
{
|
||||
double o = v;
|
||||
const double st = LotStep();
|
||||
const double lo = MinLot();
|
||||
const double hi = MaxLot();
|
||||
if(st > 0.0)
|
||||
o = MathRound(o / st) * st;
|
||||
o = MathMax(lo, MathMin(hi, o));
|
||||
o = MathMax(lo, o);
|
||||
return o;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
int OrdersPerAdd()
|
||||
{
|
||||
return MathMax(1, InpOrdersPerAdd);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
// InpMaxOrdersPerSide<=0 不限制;否则本次若开仓则持仓笔数不超过上限(含拆单)
|
||||
//+------------------------------------------------------------------+
|
||||
bool CanOpenMoreOrders(const int currentOrderCount)
|
||||
{
|
||||
if(InpMaxOrdersPerSide <= 0)
|
||||
return true;
|
||||
const int nOrd = OrdersPerAdd();
|
||||
return (currentOrderCount + nOrd <= InpMaxOrdersPerSide);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
// 将一层总手数拆成多笔(README:加仓可同时下多笔同向单),总和≈ total
|
||||
//+------------------------------------------------------------------+
|
||||
void SplitVolumeToOrders(const double total, double &parts[])
|
||||
{
|
||||
const int n = OrdersPerAdd();
|
||||
ArrayResize(parts, n);
|
||||
if(n == 1)
|
||||
{
|
||||
parts[0] = NormalizeVolume(total);
|
||||
return;
|
||||
}
|
||||
const double t = NormalizeVolume(total);
|
||||
if(t < MinLot() * n - 1e-12)
|
||||
{
|
||||
parts[0] = NormalizeVolume(total);
|
||||
for(int k = 1; k < n; k++)
|
||||
parts[k] = MinLot();
|
||||
return;
|
||||
}
|
||||
const double st = MathMax(LotStep(), 0.0000000001);
|
||||
const int steps = (int)MathRound((t - MinLot() * n) / st);
|
||||
if(steps < 0)
|
||||
{
|
||||
for(int i = 0; i < n; i++)
|
||||
parts[i] = MinLot();
|
||||
return;
|
||||
}
|
||||
const int base = steps / n;
|
||||
int rem = (int)(steps - base * n);
|
||||
for(int j = 0; j < n; j++)
|
||||
{
|
||||
const int add = base + (rem > 0 ? 1 : 0);
|
||||
if(rem > 0)
|
||||
rem--;
|
||||
parts[j] = NormalizeVolume(MinLot() + (double)add * st);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
struct SBasket
|
||||
{
|
||||
int count;
|
||||
double lastPrice; // 时间上最后一笔的开仓价
|
||||
double avgPrice; // 成本价
|
||||
double sumLots;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool BuildBasket(const ENUM_POSITION_TYPE ptype, SBasket &b)
|
||||
{
|
||||
b.count = 0;
|
||||
b.lastPrice = 0.0;
|
||||
b.avgPrice = 0.0;
|
||||
b.sumLots = 0.0;
|
||||
double sumP = 0.0;
|
||||
datetime tLast = 0;
|
||||
ulong lastTix = 0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(!g_pos.SelectByIndex(i))
|
||||
continue;
|
||||
if(g_pos.Magic() != (ulong)InpMagic)
|
||||
continue;
|
||||
if(g_pos.Symbol() != _Symbol)
|
||||
continue;
|
||||
if(g_pos.PositionType() != ptype)
|
||||
continue;
|
||||
b.count++;
|
||||
const datetime tt = g_pos.Time();
|
||||
const double op = g_pos.PriceOpen();
|
||||
const double vl = g_pos.Volume();
|
||||
const ulong tix = (ulong)g_pos.Ticket();
|
||||
sumP += op * vl;
|
||||
b.sumLots += vl;
|
||||
if(tLast == 0 || tt > tLast || (tt == tLast && tix > lastTix))
|
||||
{
|
||||
tLast = tt;
|
||||
lastTix = tix;
|
||||
b.lastPrice = op;
|
||||
}
|
||||
}
|
||||
if(b.count < 1 || b.sumLots <= 0.0)
|
||||
return false;
|
||||
b.avgPrice = sumP / b.sumLots;
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool CloseType(const ENUM_POSITION_TYPE ptype, const string reason)
|
||||
{
|
||||
bool ok = true;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(!g_pos.SelectByIndex(i))
|
||||
continue;
|
||||
if(g_pos.Magic() != (ulong)InpMagic || g_pos.Symbol() != _Symbol)
|
||||
continue;
|
||||
if(g_pos.PositionType() != ptype)
|
||||
continue;
|
||||
if(!g_trade.PositionClose(g_pos.Ticket(), InpSlippage))
|
||||
{
|
||||
ok = false;
|
||||
if(InpLog)
|
||||
Print("20260425 close fail ticket=", (ulong)g_pos.Ticket(), " err=", GetLastError());
|
||||
}
|
||||
}
|
||||
if(InpLog && ok)
|
||||
Print("20260425 全部平", ptype == POSITION_TYPE_BUY ? "多" : "空", " 原因: ", reason);
|
||||
return ok;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void RemoveTakeProfitsType(const ENUM_POSITION_TYPE ptype)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(!g_pos.SelectByIndex(i))
|
||||
continue;
|
||||
if(g_pos.Magic() != (ulong)InpMagic || g_pos.Symbol() != _Symbol)
|
||||
continue;
|
||||
if(g_pos.PositionType() != ptype)
|
||||
continue;
|
||||
if(g_pos.TakeProfit() == 0.0)
|
||||
continue;
|
||||
g_trade.PositionModify(g_pos.Ticket(), g_pos.StopLoss(), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
int PriceDigits() { return (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); }
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool OpenChunkBuy(const double vol, const string cmt)
|
||||
{
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
g_trade.SetTypeFillingBySymbol(_Symbol);
|
||||
if(!g_trade.Buy(NormalizeVolume(vol), _Symbol, 0, 0, 0, cmt))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("20260425 Buy fail vol=", vol, " err=", g_trade.ResultRetcodeDescription());
|
||||
return false;
|
||||
}
|
||||
g_sym.RefreshRates();
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool OpenChunkSell(const double vol, const string cmt)
|
||||
{
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
g_trade.SetTypeFillingBySymbol(_Symbol);
|
||||
if(!g_trade.Sell(NormalizeVolume(vol), _Symbol, 0, 0, 0, cmt))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("20260425 Sell fail vol=", vol, " err=", g_trade.ResultRetcodeDescription());
|
||||
return false;
|
||||
}
|
||||
g_sym.RefreshRates();
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool OpenBuyLayersForTotal(const int layerIdx, const string tag)
|
||||
{
|
||||
const double want = LotForLayerIndex(layerIdx);
|
||||
if(want < MinLot() - 0.0000001)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("20260425 手数过小 跳过 layer=", layerIdx);
|
||||
return false;
|
||||
}
|
||||
double parts[];
|
||||
SplitVolumeToOrders(want, parts);
|
||||
for(int k = 0; k < ArraySize(parts); k++)
|
||||
{
|
||||
if(!OpenChunkBuy(parts[k], "MGb_" + tag + "_" + IntegerToString(k)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool OpenSellLayersForTotal(const int layerIdx, const string tag)
|
||||
{
|
||||
const double want = LotForLayerIndex(layerIdx);
|
||||
if(want < MinLot() - 0.0000001)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("20260425 手数过小 跳过 layer=", layerIdx);
|
||||
return false;
|
||||
}
|
||||
double parts[];
|
||||
SplitVolumeToOrders(want, parts);
|
||||
for(int k = 0; k < ArraySize(parts); k++)
|
||||
{
|
||||
if(!OpenChunkSell(parts[k], "MGs_" + tag + "_" + IntegerToString(k)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void SetFirstLayerBuyTP()
|
||||
{
|
||||
// 仅首档(一层):每笔按该笔 **开仓价×首档%** 挂 TP;拆单时各单各自
|
||||
if(InpFirstTpPercent <= 0.0)
|
||||
return;
|
||||
g_sym.RefreshRates();
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(!g_pos.SelectByIndex(i))
|
||||
continue;
|
||||
if(g_pos.Magic() != (ulong)InpMagic || g_pos.Symbol() != _Symbol)
|
||||
continue;
|
||||
if(g_pos.PositionType() != POSITION_TYPE_BUY)
|
||||
continue;
|
||||
const double op = g_pos.PriceOpen();
|
||||
const double tpd = PctOfPrice(op, InpFirstTpPercent);
|
||||
const double tp = op + tpd;
|
||||
g_trade.PositionModify(g_pos.Ticket(), 0, NormalizeDouble(tp, PriceDigits()));
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void SetFirstLayerSellTP()
|
||||
{
|
||||
if(InpFirstTpPercent <= 0.0)
|
||||
return;
|
||||
g_sym.RefreshRates();
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(!g_pos.SelectByIndex(i))
|
||||
continue;
|
||||
if(g_pos.Magic() != (ulong)InpMagic || g_pos.Symbol() != _Symbol)
|
||||
continue;
|
||||
if(g_pos.PositionType() != POSITION_TYPE_SELL)
|
||||
continue;
|
||||
const double op = g_pos.PriceOpen();
|
||||
const double tpd = PctOfPrice(op, InpFirstTpPercent);
|
||||
const double tp = op - tpd;
|
||||
g_trade.PositionModify(g_pos.Ticket(), 0, NormalizeDouble(tp, PriceDigits()));
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void VtpLineDeleteName(const string name)
|
||||
{
|
||||
if(ObjectFind(0, name) >= 0)
|
||||
ObjectDelete(0, name);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
// >=2 档时列表「获利」为 0(程序内统盈此时尚无单笔 TP) — 用水平线标出统盈价供查看
|
||||
//+------------------------------------------------------------------+
|
||||
void VtpHLineSet(const string name, const double price, const color clr, const string toolTip)
|
||||
{
|
||||
const int dd = PriceDigits();
|
||||
const double p = NormalizeDouble(price, dd);
|
||||
if(!ObjectCreate(0, name, OBJ_HLINE, 0, 0, p))
|
||||
{
|
||||
if(ObjectFind(0, name) < 0)
|
||||
return;
|
||||
}
|
||||
ObjectSetDouble(0, name, OBJPROP_PRICE, p);
|
||||
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
|
||||
ObjectSetInteger(0, name, OBJPROP_WIDTH, InpVtpLineWidth);
|
||||
ObjectSetString(0, name, OBJPROP_TOOLTIP, toolTip);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void UpdateVirtualTPLines()
|
||||
{
|
||||
if(!InpDrawVtp)
|
||||
{
|
||||
VtpLineDeleteName(MGRID_VTP_B);
|
||||
VtpLineDeleteName(MGRID_VTP_S);
|
||||
ChartRedraw(0);
|
||||
return;
|
||||
}
|
||||
g_sym.RefreshRates();
|
||||
const int nO = MathMax(1, InpOrdersPerAdd);
|
||||
|
||||
SBasket bB, bS;
|
||||
const bool hB = BuildBasket(POSITION_TYPE_BUY, bB);
|
||||
const bool hS = BuildBasket(POSITION_TYPE_SELL, bS);
|
||||
|
||||
if(hB && bB.count > 0)
|
||||
{
|
||||
const int lay = bB.count / nO;
|
||||
const double aved = PctOfPrice(bB.avgPrice, InpAveTpPercent);
|
||||
const double tppd = PctOfPrice(bB.avgPrice, InpFirstTpPercent);
|
||||
double target;
|
||||
if(lay >= 2)
|
||||
target = bB.avgPrice + aved;
|
||||
else
|
||||
target = bB.avgPrice + (InpFirstTpPercent > 0.0 ? tppd : aved);
|
||||
VtpHLineSet(MGRID_VTP_B, target, InpVtpColorLong, "buy VTP");
|
||||
}
|
||||
else
|
||||
VtpLineDeleteName(MGRID_VTP_B);
|
||||
|
||||
if(hS && bS.count > 0)
|
||||
{
|
||||
const int layS = bS.count / nO;
|
||||
const double aved = PctOfPrice(bS.avgPrice, InpAveTpPercent);
|
||||
const double tppd = PctOfPrice(bS.avgPrice, InpFirstTpPercent);
|
||||
double tgs;
|
||||
if(layS >= 2)
|
||||
tgs = bS.avgPrice - aved;
|
||||
else
|
||||
tgs = bS.avgPrice - (InpFirstTpPercent > 0.0 ? tppd : aved);
|
||||
VtpHLineSet(MGRID_VTP_S, tgs, InpVtpColorShort, "sell VTP");
|
||||
}
|
||||
else
|
||||
VtpLineDeleteName(MGRID_VTP_S);
|
||||
|
||||
ChartRedraw(0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
// 统盈同价多笔时不可能全部挂成有效经纪商 TP,故仅图上列出「#—开仓—T目标」(与程序整篮价一致)
|
||||
//+------------------------------------------------------------------+
|
||||
void TgtListDelete()
|
||||
{
|
||||
if(ObjectFind(0, MGRID_TGT_L) >= 0)
|
||||
ObjectDelete(0, MGRID_TGT_L);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void UpdateBasketTgtListLabel()
|
||||
{
|
||||
if(!InpShowBasketTgtList)
|
||||
{
|
||||
TgtListDelete();
|
||||
return;
|
||||
}
|
||||
g_sym.RefreshRates();
|
||||
const int d = PriceDigits();
|
||||
const int nO = MathMax(1, InpOrdersPerAdd);
|
||||
|
||||
SBasket bB, bS;
|
||||
const bool hB = BuildBasket(POSITION_TYPE_BUY, bB) && bB.count > 0;
|
||||
const bool hS = BuildBasket(POSITION_TYPE_SELL, bS) && bS.count > 0;
|
||||
if(!hB && !hS)
|
||||
{
|
||||
TgtListDelete();
|
||||
return;
|
||||
}
|
||||
string txt = "20260425 统盈(程序整篮)\n(订单「获利」可能为0)\n";
|
||||
if(hB)
|
||||
{
|
||||
const int layB = bB.count / nO;
|
||||
const double aved = PctOfPrice(bB.avgPrice, InpAveTpPercent);
|
||||
const double tppd = PctOfPrice(bB.avgPrice, InpFirstTpPercent);
|
||||
const double pexB = (layB >= 2) ? bB.avgPrice + aved : bB.avgPrice + (InpFirstTpPercent > 0.0 ? tppd : aved);
|
||||
txt += "多 目标T " + DoubleToString(pexB, d) + " 成本 " + DoubleToString(bB.avgPrice, d) + "\n";
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(!g_pos.SelectByIndex(i))
|
||||
continue;
|
||||
if(g_pos.Magic() != (ulong)InpMagic || g_pos.Symbol() != _Symbol)
|
||||
continue;
|
||||
if(g_pos.PositionType() != POSITION_TYPE_BUY)
|
||||
continue;
|
||||
const ulong t = (ulong)g_pos.Ticket();
|
||||
txt += " #" + (string)(ulong)t + " 开" + DoubleToString(g_pos.PriceOpen(), d) + " →T" + DoubleToString(pexB, d) + "\n";
|
||||
}
|
||||
}
|
||||
if(hS)
|
||||
{
|
||||
const int layS = bS.count / nO;
|
||||
const double avedS = PctOfPrice(bS.avgPrice, InpAveTpPercent);
|
||||
const double tppdS = PctOfPrice(bS.avgPrice, InpFirstTpPercent);
|
||||
const double pexS = (layS >= 2) ? bS.avgPrice - avedS : bS.avgPrice - (InpFirstTpPercent > 0.0 ? tppdS : avedS);
|
||||
txt += "空 目标T " + DoubleToString(pexS, d) + " 成本 " + DoubleToString(bS.avgPrice, d) + "\n";
|
||||
for(int j = PositionsTotal() - 1; j >= 0; j--)
|
||||
{
|
||||
if(!g_pos.SelectByIndex(j))
|
||||
continue;
|
||||
if(g_pos.Magic() != (ulong)InpMagic || g_pos.Symbol() != _Symbol)
|
||||
continue;
|
||||
if(g_pos.PositionType() != POSITION_TYPE_SELL)
|
||||
continue;
|
||||
const ulong t2 = (ulong)g_pos.Ticket();
|
||||
txt += " #" + (string)(ulong)t2 + " 开" + DoubleToString(g_pos.PriceOpen(), d) + " →T" + DoubleToString(pexS, d) + "\n";
|
||||
}
|
||||
}
|
||||
const int mlen = 1800; // 防止过长
|
||||
if(StringLen(txt) > mlen)
|
||||
txt = StringSubstr(txt, 0, mlen) + "…";
|
||||
|
||||
if(!ObjectCreate(0, MGRID_TGT_L, OBJ_LABEL, 0, 0, 0))
|
||||
{
|
||||
if(ObjectFind(0, MGRID_TGT_L) < 0)
|
||||
return;
|
||||
}
|
||||
ObjectSetInteger(0, MGRID_TGT_L, OBJPROP_CORNER, CORNER_LEFT_LOWER);
|
||||
ObjectSetInteger(0, MGRID_TGT_L, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
|
||||
ObjectSetInteger(0, MGRID_TGT_L, OBJPROP_XDISTANCE, 6);
|
||||
ObjectSetInteger(0, MGRID_TGT_L, OBJPROP_YDISTANCE, 8);
|
||||
ObjectSetString(0, MGRID_TGT_L, OBJPROP_TEXT, txt);
|
||||
ObjectSetInteger(0, MGRID_TGT_L, OBJPROP_COLOR, InpTgtListColor);
|
||||
ObjectSetInteger(0, MGRID_TGT_L, OBJPROP_FONTSIZE, InpTgtListFont);
|
||||
ObjectSetString(0, MGRID_TGT_L, OBJPROP_FONT, "Arial");
|
||||
ChartRedraw(0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void ProcessLongSide()
|
||||
{
|
||||
SBasket b;
|
||||
g_sym.RefreshRates();
|
||||
const int nOrd = OrdersPerAdd();
|
||||
if(!BuildBasket(POSITION_TYPE_BUY, b))
|
||||
{
|
||||
if(!SpreadOk())
|
||||
return;
|
||||
if(!CanOpenMoreOrders(0))
|
||||
return;
|
||||
if(!OpenBuyLayersForTotal(0, "0"))
|
||||
return;
|
||||
SetFirstLayerBuyTP();
|
||||
return;
|
||||
}
|
||||
const int layers = b.count / nOrd;
|
||||
|
||||
const double dis = PctOfPrice(b.lastPrice, InpDisPercent);
|
||||
const double ave = PctOfPrice(b.avgPrice, InpAveTpPercent);
|
||||
|
||||
// 1) 统盈: >=2 档 用成本价+统盈%;1 档 靠首档TP(或首档%=0 时在此统盈)
|
||||
g_sym.RefreshRates();
|
||||
if(layers >= 2)
|
||||
{
|
||||
if(g_sym.Bid() >= b.avgPrice + ave)
|
||||
{
|
||||
CloseType(POSITION_TYPE_BUY, "统盈(>=2档)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(InpFirstTpPercent <= 0.0 && g_sym.Bid() >= b.avgPrice + ave)
|
||||
{
|
||||
CloseType(POSITION_TYPE_BUY, "单档无首档TP%,按成本+统盈%");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 加仓: 与上一笔成交价(多=Ask)相对 last 至少 上一×Dis%
|
||||
g_sym.RefreshRates();
|
||||
if(g_sym.Ask() <= b.lastPrice - dis)
|
||||
{
|
||||
if(!CanOpenMoreOrders(b.count))
|
||||
return;
|
||||
if(!SpreadOk())
|
||||
return;
|
||||
if(b.count > 0)
|
||||
RemoveTakeProfitsType(POSITION_TYPE_BUY);
|
||||
const int nextLayer = b.count / nOrd; // 下一档索引 = 已满仓数
|
||||
if(!OpenBuyLayersForTotal(nextLayer, IntegerToString(nextLayer)))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void ProcessShortSide()
|
||||
{
|
||||
SBasket b;
|
||||
g_sym.RefreshRates();
|
||||
const int nOrd = OrdersPerAdd();
|
||||
if(!BuildBasket(POSITION_TYPE_SELL, b))
|
||||
{
|
||||
if(!SpreadOk())
|
||||
return;
|
||||
if(!CanOpenMoreOrders(0))
|
||||
return;
|
||||
if(!OpenSellLayersForTotal(0, "0"))
|
||||
return;
|
||||
SetFirstLayerSellTP();
|
||||
return;
|
||||
}
|
||||
const int layers = b.count / nOrd;
|
||||
|
||||
const double dis = PctOfPrice(b.lastPrice, InpDisPercent);
|
||||
const double ave = PctOfPrice(b.avgPrice, InpAveTpPercent);
|
||||
|
||||
g_sym.RefreshRates();
|
||||
if(layers >= 2)
|
||||
{
|
||||
if(g_sym.Ask() <= b.avgPrice - ave)
|
||||
{
|
||||
CloseType(POSITION_TYPE_SELL, "统盈(>=2档)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(InpFirstTpPercent <= 0.0 && g_sym.Ask() <= b.avgPrice - ave)
|
||||
{
|
||||
CloseType(POSITION_TYPE_SELL, "单档无首档TP%,按成本+统盈%");
|
||||
return;
|
||||
}
|
||||
}
|
||||
g_sym.RefreshRates();
|
||||
if(g_sym.Bid() >= b.lastPrice + dis)
|
||||
{
|
||||
if(!CanOpenMoreOrders(b.count))
|
||||
return;
|
||||
if(!SpreadOk())
|
||||
return;
|
||||
if(b.count > 0)
|
||||
RemoveTakeProfitsType(POSITION_TYPE_SELL);
|
||||
const int nextLayer = b.count / nOrd;
|
||||
if(!OpenSellLayersForTotal(nextLayer, IntegerToString(nextLayer)))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
g_sym.Name(_Symbol);
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
InitLotLadder();
|
||||
LogInitDiagnostics();
|
||||
if(InpAddTimes < 1.0)
|
||||
{
|
||||
Print("20260425: AddTimes 应 >=1");
|
||||
return INIT_PARAMETERS_INCORRECT;
|
||||
}
|
||||
if(InpDisPercent <= 0.0)
|
||||
{
|
||||
Print("20260425: InpDisPercent 应 >0");
|
||||
return INIT_PARAMETERS_INCORRECT;
|
||||
}
|
||||
if(InpAveTpPercent <= 0.0)
|
||||
{
|
||||
Print("20260425: InpAveTpPercent 应 >0");
|
||||
return INIT_PARAMETERS_INCORRECT;
|
||||
}
|
||||
if(InpFirstTpPercent < 0.0)
|
||||
{
|
||||
Print("20260425: InpFirstTpPercent 应 >=0");
|
||||
return INIT_PARAMETERS_INCORRECT;
|
||||
}
|
||||
if(LotForLayerIndex(0) < MinLot() - 1e-8)
|
||||
{
|
||||
Print("20260425: 第1档手数小于平台最小手,请改 InpLotLadder[0] 或品种");
|
||||
return INIT_PARAMETERS_INCORRECT;
|
||||
}
|
||||
if(InpMaxOrdersPerSide > 0 && OrdersPerAdd() > InpMaxOrdersPerSide)
|
||||
{
|
||||
Print("20260425: InpOrdersPerAdd 不能大于 InpMaxOrdersPerSide(否则无法开首单)");
|
||||
return INIT_PARAMETERS_INCORRECT;
|
||||
}
|
||||
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
|
||||
{
|
||||
Print("20260425: 自动交易已关闭(终端设置)");
|
||||
}
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
VtpLineDeleteName(MGRID_VTP_B);
|
||||
VtpLineDeleteName(MGRID_VTP_S);
|
||||
TgtListDelete();
|
||||
ChartRedraw(0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
g_sym.RefreshRates();
|
||||
// 多、空 两边独立
|
||||
ProcessLongSide();
|
||||
ProcessShortSide();
|
||||
UpdateVirtualTPLines();
|
||||
UpdateBasketTgtListLabel();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# 马丁网格策略
|
||||
|
||||
- **概念**:`InpSlippage` 等仍按「点」= `SYMBOL_POINT`;但 **首档止盈 / 统盈 / 加仓间距** 在 v1.08 起为**相对价**的**百分比(%)**,在运行时以对应参考价(开仓/成本/上一同向开)换算成实际价距。语言 MQL5。
|
||||
- **特点**:小周期震荡、同向加仓、整篮按成本统盈(无单张止损)。
|
||||
|
||||
## 功能概要
|
||||
|
||||
震荡中同向加仓;无单时开首单;首档按 **InpFirstTpPercent** 相对**该笔开仓**设止盈(价距=开仓价×%);多档后按**加权成本 × InpAveTpPercent%** 整篮平仓;加仓间距= **上一同向单开仓价 × InpDisPercent%**;多空对称(v1.10 起已取消按 K 线数的时间全平)。
|
||||
|
||||
## 参数(与输入组对应)
|
||||
|
||||
| 思路 | 参数 |
|
||||
|------|------|
|
||||
| 滑点 | `InpSlippage` |
|
||||
| 识别 | `InpMagic` |
|
||||
| 首档 TP% / 统盈%(相对成本) | `InpFirstTpPercent`(默认0.05), `InpAveTpPercent`(默认0.05) |
|
||||
| 加仓间距%(相对上笔同向开) / 满 20 档后倍率 / 单方向最大持仓笔数 | `InpDisPercent`(默认0.1), `InpAddTimes`(默认1.2), `InpMaxOrdersPerSide`(默认20,0=不限制;达到后仅停止新开仓,不平仓) |
|
||||
| 第 1~20 档总手数(一行 20 个数,英文逗号) | `InpLotLadder` |
|
||||
| 每档拆单笔数 | `InpOrdersPerAdd` |
|
||||
| 点差上限、日志 | `InpMaxSpreadPoints`, `InpLog` |
|
||||
| 图表虚拟统盈线 | `InpDrawVtp` 及颜色、线宽 |
|
||||
| 图表每单统盈价列表 | `InpShowBasketTgtList`, `InpTgtListColor`, `InpTgtListFont`(左下列出 #、开仓、统盈目标 T,与程序整篮价一致,非交易列表「获利」列) |
|
||||
|
||||
## 止盈策略
|
||||
|
||||
**档**:同向第几批加仓;若 `InpOrdersPerAdd>1`,档数 = 该向笔数 / 拆单。
|
||||
|
||||
- **仅 1 档**
|
||||
- `InpFirstTpPercent > 0`:在**订单上**挂 TP(多 `开 + 开×%`,空 `开 − 开×%`)。
|
||||
- `InpFirstTpPercent = 0`:不挂首档 TP,改由程序在 **多** `Bid ≥ 成本 + 成本×InpAveTpPercent%`、**空** `Ask ≤ 成本 − 成本×InpAveTpPercent%` 时整篮平仓。
|
||||
- **≥2 档**:加第 2 档前会清掉该向各单 TP,之后只在 **多** `Bid ≥ 成本 + 成本×统盈%`、**空** `Ask ≤ 成本 − 成本×统盈%` 时**整篮**平仓。
|
||||
- 市价加仓判定:多关注 **Ask** 与上一开间距;空 **Bid** 与上一开(与 v1.08 前仅用对侧价相比更贴实际成价)。
|
||||
- **图表线** `InpDrawVtp`:仅作目标价参考,非经纪商挂单。
|
||||
- **每单看统盈价**:`InpShowBasketTgtList=true` 时,在**图表左下**用文本列出该向每笔 `#`、开仓、以及**同一条**统盈目标 T(与程序整篮平仓一致)。**交易**窗口里多档时「获利」仍可能全 0:统盈是程序按成本判断,**不能把同一统盈价在每一笔上都设成有效经纪商 TP**(否则或拒单、或只平部分单),故用图上列表作对照。
|
||||
@@ -1,25 +0,0 @@
|
||||
# 马丁网格策略
|
||||
|
||||
## 基础概念
|
||||
- 平台:MQL5
|
||||
- 品种:支持跟随图表窗口
|
||||
- 所有日志、参数说明均需使用中文
|
||||
|
||||
## 辅助指标
|
||||
- 移动平均线(Moving Average)
|
||||
|
||||
## 核心逻辑
|
||||
- K线周期:支持参数设置,默认为1min
|
||||
- 突破K线:开仓价在均线之下,收盘价在均线之上,称之为多单突破K线,反之亦然
|
||||
- 出现突破K线时,开首单,如果已有同方向单,不开首单
|
||||
- 价格往反方向运行 首单价*补仓比例(支持参数设置) 时,即为满足加仓条件
|
||||
- 首单价开仓手数支持参数设置
|
||||
- 加仓放大手数支持参数设置,默认为 0.05手,相比上一组的手数 + 加仓放大手数;**不是每一网格档都乘一次**,而是与下条「档间隔」配合使用。
|
||||
- **档间隔(参数 N)**:控制隔多少档才把「加仓放大手数」用上一次。例如 **N = 5** 表示每 **5 个网格档**为一组,**组内手数相同**;每进入新的一组,手数 = **上一组手数 + 加仓放大手数**(首组 = 首单手数)。用于减缓手数膨胀。
|
||||
- 平仓逻辑:使用统盈的策略,多单止盈价 = 加权平均成本 + 加权平均成本 * 止盈比例(支持参数设置),反之亦然
|
||||
- 多空单互不影响
|
||||
- 支持参数设置是否复利,复利开启后,只会影响首单的开仓手数,不影响加仓的开仓手数
|
||||
|
||||
## 补充逻辑
|
||||
- 单向持仓已达「单向最大档数」时,**每个 Tick** 比较该向**浮动盈亏**(各单获利+库存费之和,账户货币)与 **账户结余 × 浮盈比例**(参数 `InpMaxLayersFloatPct`,百分比;为 **0** 表示关闭本规则)。若浮盈不低于阈值,则**平掉该方向、本品种、本 EA Magic 下全部持仓**,随后可再由突破逻辑开新首单。
|
||||
- **结余**指 `ACCOUNT_BALANCE`。与统盈可同时存在:若价格已达统盈会先整篮平掉,否则在满档下仍可能触发本条。
|
||||
@@ -1,562 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 马丁网格策略 EA — 逻辑见同目录 README.md |
|
||||
//| 需对冲账户(同一方向可多笔持仓);日志与参数说明为中文。 |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "zyb-ea"
|
||||
#property version "1.00"
|
||||
#property description "马丁网格:MA 突破首单 + 首单价比例间距补仓 + 档间隔每组+#加仓放大手数 + 统盈;可选复利仅首单。"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
CTrade g_trade;
|
||||
|
||||
int g_hMa = INVALID_HANDLE;
|
||||
|
||||
//------------------------------------------------------------
|
||||
input group "工作区"
|
||||
input ENUM_TIMEFRAMES InpWorkTF = PERIOD_M1; // 工作周期(K 线:突破判定、新 K 检测;默认 1 分钟)
|
||||
input int InpSlippage = 30; // 滑点(点)
|
||||
input int InpMaxSpreadPoints = 0; // 最大点差(点,0=不限制)
|
||||
input long InpMagic = 20260507; // Magic 识别码
|
||||
|
||||
input group "移动平均线"
|
||||
input int InpMAPeriod = 20; // MA 周期
|
||||
input int InpMAShift = 0; // MA 位移
|
||||
input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // MA 算法
|
||||
input ENUM_APPLIED_PRICE InpMAPrice = PRICE_CLOSE; // 应用于
|
||||
|
||||
input group "头寸与补仓"
|
||||
input double InpFirstLot = 0.01; // 首单手数(复利关闭时为固定手数)
|
||||
input double InpStepPercent = 2.0; // 补仓比例(%):每档价距 = 首单开仓价 × 本参数%(相对上一同向开仓反向计数)
|
||||
input double InpAddLotBoost = 0.05; // 加仓放大手数:每新一组在上一组手数上 + 本参数(组内 N 档相同;首组=首单)
|
||||
input int InpTierInterval = 5; // 档间隔 N:每 N 个网格档为一组,组满后下一组手数 + 加仓放大手数
|
||||
input int InpMaxLayers = 50; // 单向最大档数(含首单,安全上限)
|
||||
|
||||
input group "统盈平仓"
|
||||
input double InpTpPercent = 10.0; // 止盈比例(%):多单目标 = 加权成本 × (1+本%);空单反之
|
||||
|
||||
input group "补充逻辑(满档浮盈)"
|
||||
input double InpMaxLayersFloatPct = 0.0; // 浮盈比例(%,相对账户结余):该向已达「单向最大档」时,若该向浮盈(含库存费)≥结余×本%/100 则平掉该向全仓;0=关闭
|
||||
|
||||
input group "复利(仅首单)"
|
||||
input bool InpUseCompound = false; // 开启后只按比例放大「首单」手数;加仓档仍用本节基准手数递推
|
||||
input double InpCompoundRefEquity = 10000.0; // 参考净值:首单手数 ≈ InpFirstLot × (当前净值 / 参考净值)
|
||||
|
||||
input group "其它"
|
||||
input bool InpLog = true; // 是否打印日志
|
||||
|
||||
//------------------------------------------------------------
|
||||
double g_longBaseLot = 0.0; // 本轮回多单首单基准手数(加仓按此递推)
|
||||
double g_shortBaseLot = 0.0;
|
||||
static datetime g_lastWorkBar = 0;
|
||||
|
||||
//------------------------------------------------------------
|
||||
double MinLot() { return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); }
|
||||
double MaxLot() { return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); }
|
||||
double LotStep() { return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); }
|
||||
|
||||
double NormalizeVolumeVal(const double v)
|
||||
{
|
||||
const double lo = MinLot();
|
||||
const double hi = MaxLot();
|
||||
const double st = LotStep();
|
||||
if(st <= 0.0 || hi < lo)
|
||||
return v;
|
||||
if(v < lo - 1e-12)
|
||||
return 0.0;
|
||||
double x = MathMin(v, hi);
|
||||
x = MathFloor(x / st + 1e-12) * st;
|
||||
return x;
|
||||
}
|
||||
|
||||
int SpreadPoints()
|
||||
{
|
||||
return (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// 复利仅影响首单:返回本轮「基准手数」,加仓用 LotForTier(..., 此基准)
|
||||
double CalcBasketBaseLot()
|
||||
{
|
||||
double lot = InpFirstLot;
|
||||
if(InpUseCompound && InpCompoundRefEquity > 1e-8)
|
||||
{
|
||||
const double eq = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
lot = InpFirstLot * (eq / InpCompoundRefEquity);
|
||||
}
|
||||
return NormalizeVolumeVal(lot);
|
||||
}
|
||||
|
||||
// tierIndex:0=首单,1=第 2 笔 …;grp=tierIndex/N 为组号,组内手数相同,每组手数=首单基准+grp×加仓放大手数
|
||||
double LotForTier(const int tierIndex, const double basketBaseLot)
|
||||
{
|
||||
if(tierIndex < 0 || basketBaseLot <= 0.0)
|
||||
return 0.0;
|
||||
const int n = MathMax(1, InpTierInterval);
|
||||
const int grp = tierIndex / n;
|
||||
if(InpAddLotBoost <= 0.0)
|
||||
return NormalizeVolumeVal(basketBaseLot);
|
||||
double vol = basketBaseLot + (double)grp * InpAddLotBoost;
|
||||
vol = MathMin(vol, MaxLot());
|
||||
return NormalizeVolumeVal(vol);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
int CountPositions(const long typeFilter) // POSITION_TYPE_BUY / SELL,-1 表示不限方向但同 magic 同品种
|
||||
{
|
||||
int c = 0;
|
||||
const int total = (int)PositionsTotal();
|
||||
for(int i = 0; i < total; i++)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0 || !PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
continue;
|
||||
const long typ = (long)PositionGetInteger(POSITION_TYPE);
|
||||
if(typeFilter >= 0 && typ != typeFilter)
|
||||
continue;
|
||||
c++;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
bool GetBasketEdges(const long posType, double &firstOpen, double &lastOpen, datetime &firstTime, datetime &lastTime)
|
||||
{
|
||||
firstOpen = 0.0;
|
||||
lastOpen = 0.0;
|
||||
firstTime = 0;
|
||||
lastTime = 0;
|
||||
bool any = false;
|
||||
const int total = (int)PositionsTotal();
|
||||
for(int i = 0; i < total; i++)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0 || !PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_TYPE) != posType)
|
||||
continue;
|
||||
const double op = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const datetime tm = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
if(!any)
|
||||
{
|
||||
firstOpen = lastOpen = op;
|
||||
firstTime = lastTime = tm;
|
||||
any = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(tm <= firstTime)
|
||||
{
|
||||
firstTime = tm;
|
||||
firstOpen = op;
|
||||
}
|
||||
if(tm >= lastTime)
|
||||
{
|
||||
lastTime = tm;
|
||||
lastOpen = op;
|
||||
}
|
||||
}
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
bool BasketAvgPrice(const long posType, double &avg, double &sumLots)
|
||||
{
|
||||
avg = 0.0;
|
||||
sumLots = 0.0;
|
||||
double sumPxVol = 0.0;
|
||||
const int total = (int)PositionsTotal();
|
||||
for(int i = 0; i < total; i++)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0 || !PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_TYPE) != posType)
|
||||
continue;
|
||||
const double v = PositionGetDouble(POSITION_VOLUME);
|
||||
const double p = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
sumPxVol += p * v;
|
||||
sumLots += v;
|
||||
}
|
||||
if(sumLots <= 1e-12)
|
||||
return false;
|
||||
avg = sumPxVol / sumLots;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 该向持仓浮动盈亏(账户货币,含库存费)
|
||||
double BasketFloatingPL(const long posType)
|
||||
{
|
||||
double sum = 0.0;
|
||||
const int total = (int)PositionsTotal();
|
||||
for(int i = 0; i < total; i++)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0 || !PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_TYPE) != posType)
|
||||
continue;
|
||||
sum += PositionGetDouble(POSITION_PROFIT);
|
||||
sum += PositionGetDouble(POSITION_SWAP);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
bool CloseAllDirection(const long posType, const string reason)
|
||||
{
|
||||
ulong tickets[];
|
||||
int n = 0;
|
||||
const int total = (int)PositionsTotal();
|
||||
for(int i = 0; i < total; i++)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0 || !PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
continue;
|
||||
if((long)PositionGetInteger(POSITION_TYPE) != posType)
|
||||
continue;
|
||||
ArrayResize(tickets, n + 1);
|
||||
tickets[n++] = ticket;
|
||||
}
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
bool ok = true;
|
||||
for(int j = 0; j < n; j++)
|
||||
{
|
||||
if(!g_trade.PositionClose(tickets[j], (uint)InpSlippage))
|
||||
{
|
||||
ok = false;
|
||||
if(InpLog)
|
||||
Print("平仓失败 ticket=", tickets[j], " err=", GetLastError());
|
||||
}
|
||||
}
|
||||
if(InpLog && ok && n > 0)
|
||||
Print("批量平仓 ", (posType == POSITION_TYPE_BUY ? "多" : "空"), " 笔数=", n, " 原因: ", reason);
|
||||
return ok;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
void CheckBasketTakeProfit()
|
||||
{
|
||||
const int nb = CountPositions(POSITION_TYPE_BUY);
|
||||
if(nb > 0)
|
||||
{
|
||||
double avg = 0.0, sumL = 0.0;
|
||||
if(BasketAvgPrice(POSITION_TYPE_BUY, avg, sumL))
|
||||
{
|
||||
const double target = avg * (1.0 + InpTpPercent / 100.0);
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
if(bid >= target)
|
||||
CloseAllDirection(POSITION_TYPE_BUY, "多单统盈");
|
||||
}
|
||||
}
|
||||
|
||||
const int ns = CountPositions(POSITION_TYPE_SELL);
|
||||
if(ns > 0)
|
||||
{
|
||||
double avg = 0.0, sumL = 0.0;
|
||||
if(BasketAvgPrice(POSITION_TYPE_SELL, avg, sumL))
|
||||
{
|
||||
const double target = avg * (1.0 - InpTpPercent / 100.0);
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
if(ask <= target)
|
||||
CloseAllDirection(POSITION_TYPE_SELL, "空单统盈");
|
||||
}
|
||||
}
|
||||
|
||||
if(CountPositions(POSITION_TYPE_BUY) == 0)
|
||||
g_longBaseLot = 0.0;
|
||||
if(CountPositions(POSITION_TYPE_SELL) == 0)
|
||||
g_shortBaseLot = 0.0;
|
||||
}
|
||||
|
||||
// 已达单向最大档时:浮盈 ≥ 结余×比例 则清该向仓位(重头在平仓后由突破逻辑再开)
|
||||
void CheckMaxLayersBalanceFloatExit()
|
||||
{
|
||||
if(InpMaxLayersFloatPct <= 0.0)
|
||||
return;
|
||||
|
||||
const double bal = AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
if(bal <= 0.0)
|
||||
return;
|
||||
|
||||
const double need = bal * (InpMaxLayersFloatPct / 100.0);
|
||||
|
||||
const int nb = CountPositions(POSITION_TYPE_BUY);
|
||||
if(nb == InpMaxLayers)
|
||||
{
|
||||
const double fpl = BasketFloatingPL(POSITION_TYPE_BUY);
|
||||
if(fpl >= need)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("满档多单补充平仓: 浮盈=", fpl, " 阈值(结余×", InpMaxLayersFloatPct, "%)=", need);
|
||||
CloseAllDirection(POSITION_TYPE_BUY, "满档-浮盈达结余×设定比例");
|
||||
}
|
||||
}
|
||||
|
||||
const int ns = CountPositions(POSITION_TYPE_SELL);
|
||||
if(ns == InpMaxLayers)
|
||||
{
|
||||
const double fpl = BasketFloatingPL(POSITION_TYPE_SELL);
|
||||
if(fpl >= need)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("满档空单补充平仓: 浮盈=", fpl, " 阈值(结余×", InpMaxLayersFloatPct, "%)=", need);
|
||||
CloseAllDirection(POSITION_TYPE_SELL, "满档-浮盈达结余×设定比例");
|
||||
}
|
||||
}
|
||||
|
||||
if(CountPositions(POSITION_TYPE_BUY) == 0)
|
||||
g_longBaseLot = 0.0;
|
||||
if(CountPositions(POSITION_TYPE_SELL) == 0)
|
||||
g_shortBaseLot = 0.0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
bool CopyBar1OcMa(double &o1, double &c1, double &ma1)
|
||||
{
|
||||
double o[], c[];
|
||||
ArraySetAsSeries(o, true);
|
||||
ArraySetAsSeries(c, true);
|
||||
if(CopyOpen(_Symbol, InpWorkTF, 1, 1, o) != 1)
|
||||
return false;
|
||||
if(CopyClose(_Symbol, InpWorkTF, 1, 1, c) != 1)
|
||||
return false;
|
||||
double m[];
|
||||
ArraySetAsSeries(m, true);
|
||||
if(CopyBuffer(g_hMa, 0, 1, 1, m) != 1)
|
||||
return false;
|
||||
o1 = o[0];
|
||||
c1 = c[0];
|
||||
ma1 = m[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
// 新 K 线开盘时:用上一根已收盘 K 做突破判定;仅在无同向持仓时开首单
|
||||
void TryOpenFirstOnBreakout()
|
||||
{
|
||||
if(g_hMa == INVALID_HANDLE)
|
||||
return;
|
||||
|
||||
double o1 = 0.0, c1 = 0.0, ma1 = 0.0;
|
||||
if(!CopyBar1OcMa(o1, c1, ma1))
|
||||
return;
|
||||
|
||||
const bool longBreak = (o1 < ma1 && c1 > ma1);
|
||||
const bool shortBreak = (o1 > ma1 && c1 < ma1);
|
||||
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
|
||||
if(longBreak && CountPositions(POSITION_TYPE_BUY) == 0)
|
||||
{
|
||||
g_longBaseLot = CalcBasketBaseLot();
|
||||
if(g_longBaseLot >= MinLot() - 1e-12)
|
||||
{
|
||||
const string cmt = "马丁-多首单-突破";
|
||||
if(g_trade.Buy(g_longBaseLot, _Symbol, 0.0, 0.0, 0.0, cmt))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("开多单首单 手数=", g_longBaseLot);
|
||||
}
|
||||
else if(InpLog)
|
||||
Print("开多单首单失败 err=", GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(InpLog)
|
||||
Print("多单首单手数过小,跳过");
|
||||
g_longBaseLot = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if(shortBreak && CountPositions(POSITION_TYPE_SELL) == 0)
|
||||
{
|
||||
g_shortBaseLot = CalcBasketBaseLot();
|
||||
if(g_shortBaseLot >= MinLot() - 1e-12)
|
||||
{
|
||||
const string cmt = "马丁-空首单-突破";
|
||||
if(g_trade.Sell(g_shortBaseLot, _Symbol, 0.0, 0.0, 0.0, cmt))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("开空单首单 手数=", g_shortBaseLot);
|
||||
}
|
||||
else if(InpLog)
|
||||
Print("开空单首单失败 err=", GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(InpLog)
|
||||
Print("空单首单手数过小,跳过");
|
||||
g_shortBaseLot = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
void TryGridAddBuy()
|
||||
{
|
||||
const int n = CountPositions(POSITION_TYPE_BUY);
|
||||
if(n < 1 || n >= InpMaxLayers)
|
||||
return;
|
||||
double fo = 0.0, lo = 0.0;
|
||||
datetime ft, lt;
|
||||
if(!GetBasketEdges(POSITION_TYPE_BUY, fo, lo, ft, lt))
|
||||
return;
|
||||
if(g_longBaseLot <= 0.0)
|
||||
g_longBaseLot = CalcBasketBaseLot(); // 若未设置(如手工单),尽力 fallback
|
||||
|
||||
const double dPrice = fo * (InpStepPercent / 100.0);
|
||||
if(dPrice <= 0.0)
|
||||
return;
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
if(bid <= lo - dPrice)
|
||||
{
|
||||
const int nextTier = n; // 当前已有 n 笔,下一笔档位索引为 n(首单为 0)
|
||||
double vol = LotForTier(nextTier, g_longBaseLot);
|
||||
if(vol < MinLot() - 1e-12)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("多单加仓手数过小 tier=", nextTier, " 跳过");
|
||||
return;
|
||||
}
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
const bool lastToCap = (n + 1 == InpMaxLayers); // 本次成交后达到单向最大档数(含首单)
|
||||
const string cmt = lastToCap ? "马丁-多加仓-已达最大档(末笔·不再加仓)" : "马丁-多加仓";
|
||||
if(g_trade.Buy(vol, _Symbol, 0.0, 0.0, 0.0, cmt))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("多加仓 第", (nextTier + 1), "/", InpMaxLayers, "笔 手数=", vol,
|
||||
(lastToCap ? " 【已达单向最大档,后续不再加仓】" : ""),
|
||||
" bid=", bid, " 上开=", lo, " 首开=", fo);
|
||||
}
|
||||
else if(InpLog)
|
||||
Print("多加仓失败 err=", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
void TryGridAddSell()
|
||||
{
|
||||
const int n = CountPositions(POSITION_TYPE_SELL);
|
||||
if(n < 1 || n >= InpMaxLayers)
|
||||
return;
|
||||
double fo = 0.0, lo = 0.0;
|
||||
datetime ft, lt;
|
||||
if(!GetBasketEdges(POSITION_TYPE_SELL, fo, lo, ft, lt))
|
||||
return;
|
||||
if(g_shortBaseLot <= 0.0)
|
||||
g_shortBaseLot = CalcBasketBaseLot();
|
||||
|
||||
const double dPrice = fo * (InpStepPercent / 100.0);
|
||||
if(dPrice <= 0.0)
|
||||
return;
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
if(ask >= lo + dPrice)
|
||||
{
|
||||
const int nextTier = n;
|
||||
double vol = LotForTier(nextTier, g_shortBaseLot);
|
||||
if(vol < MinLot() - 1e-12)
|
||||
{
|
||||
if(InpLog)
|
||||
Print("空单加仓手数过小 tier=", nextTier, " 跳过");
|
||||
return;
|
||||
}
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
const bool lastToCap = (n + 1 == InpMaxLayers);
|
||||
const string cmt = lastToCap ? "马丁-空加仓-已达最大档(末笔·不再加仓)" : "马丁-空加仓";
|
||||
if(g_trade.Sell(vol, _Symbol, 0.0, 0.0, 0.0, cmt))
|
||||
{
|
||||
if(InpLog)
|
||||
Print("空加仓 第", (nextTier + 1), "/", InpMaxLayers, "笔 手数=", vol,
|
||||
(lastToCap ? " 【已达单向最大档,后续不再加仓】" : ""),
|
||||
" ask=", ask, " 上开=", lo, " 首开=", fo);
|
||||
}
|
||||
else if(InpLog)
|
||||
Print("空加仓失败 err=", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
int OnInit()
|
||||
{
|
||||
g_trade.SetExpertMagicNumber((ulong)InpMagic);
|
||||
g_trade.SetDeviationInPoints((uint)InpSlippage);
|
||||
const int filling = (int)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
|
||||
if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
else if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
else
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||||
|
||||
g_hMa = iMA(_Symbol, InpWorkTF, InpMAPeriod, InpMAShift, InpMAMethod, InpMAPrice);
|
||||
if(g_hMa == INVALID_HANDLE)
|
||||
{
|
||||
Print("初始化 MA 句柄失败");
|
||||
return INIT_FAILED;
|
||||
}
|
||||
g_lastWorkBar = 0;
|
||||
g_longBaseLot = 0.0;
|
||||
g_shortBaseLot = 0.0;
|
||||
if(InpLog)
|
||||
Print("马丁网格 EA 初始化: ", _Symbol, " 工作周期=", EnumToString(InpWorkTF),
|
||||
" MA=", InpMAPeriod,
|
||||
" 补仓%=", InpStepPercent,
|
||||
" 加仓放大手数=", InpAddLotBoost, " 档间隔=", InpTierInterval,
|
||||
" 统盈%=", InpTpPercent,
|
||||
" 满档浮盈阈值%=", InpMaxLayersFloatPct,
|
||||
" 复利=", (InpUseCompound ? "开" : "关"));
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_hMa != INVALID_HANDLE)
|
||||
{
|
||||
IndicatorRelease(g_hMa);
|
||||
g_hMa = INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(InpMaxSpreadPoints > 0 && SpreadPoints() > InpMaxSpreadPoints)
|
||||
return;
|
||||
|
||||
CheckBasketTakeProfit();
|
||||
CheckMaxLayersBalanceFloatExit();
|
||||
|
||||
const datetime bar0 = iTime(_Symbol, InpWorkTF, 0);
|
||||
if(bar0 != 0 && bar0 != g_lastWorkBar)
|
||||
{
|
||||
g_lastWorkBar = bar0;
|
||||
TryOpenFirstOnBreakout();
|
||||
}
|
||||
|
||||
TryGridAddBuy();
|
||||
TryGridAddSell();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
Reference in New Issue
Block a user