|
|
//+------------------------------------------------------------------+
//| 摸金校尉-MT5【2/3位小数自动适应版】.mq5 |
//| GSD |
//| WeChat:Mirootsoft|
//+------------------------------------------------------------------+
#property copyright " Mirootsoft"
#property link ""
#property version "1.15"
#property description "免责声明:"
#property description "1.过去的收益不代表未来的收益"
#property description "2.账号产生的盈亏客户需要自己承担"
#property description "3.本EA只作为交易辅助工具,不作任何盈利保证"
#property description "4.EA属于虚拟产品一旦发货正常加载使用不支持退货退款"
#property description "5.任何策略都有一定的风险,不局限于代码问题,BUG,行情问题"
#include <Trade/Trade.mqh>
#include <Trade/OrderInfo.mqh>
#include <Trade/PositionInfo.mqh>
#include <Trade/HistoryOrderInfo.mqh>
#include <Arrays/ArrayInt.mqh>
CTrade Trade;
COrderInfo OrderInfo;
CPositionInfo PositionInfo;
CHistoryOrderInfo HistoryOrder;
// 顺势距离计算模式
enum ENUM_ST_DISTANCE_MODE
{
ST_DISTANCE_FIXED = 0, // 固定距离(兼容旧版)
ST_DISTANCE_PERCENT = 1 // 按价格百分比动态计算
};
// 输入参数
input group "━━━━ 通用设置 ━━━━"
input double magicNumber = 123; // 魔术码
input double 开仓倍数 = 1; // 全局开仓倍数
input string comm = "V:fanfans555"; // 订单注释
input double FirstStep = 80; // 首仓挂单间距
input double StopLoss = 9999; // 强止损金额(ST模式=Basket总止损;原始模式=单方向止损)
input bool sj = false; // 时间过滤
input int star = 1; // 开始时间
input int end = 23; // 结束时间
input bool allow_sell = true; // 允许做空
input bool allow_buy = true; // 允许做多
input group "━━━━ 界面显示 ━━━━"
input bool 启用恒鑫风格界面 = true; // 深色卡片式交易面板
input bool 启用恒鑫图表配色 = true; // 黑金/红绿K线配色
input int 界面X = 20; // 面板左边距
input int 界面Y = 25; // 面板上边距
input int 界面宽度 = 720; // 面板宽度
input bool 显示均价止盈线 = true; // 主图显示持仓均价/当前止盈目标线
input group "━━━━ ST顺势 + 逆势恢复模式 ━━━━"
input bool 启用ST顺势逆势恢复模式 = true; // 开=ST顺势开新仓+逆势只恢复旧仓;关=V1.07原始逆势模式
input int Supertrend_ATR周期 = 10; // ST ATR周期
input double Supertrend_倍数 = 3.0; // ST倍数
input int ST趋势确认K线数 = 1; // 连续已收盘K线同方向后确认(1~3)
input bool 显示Supertrend = true; // K线主图显示ST
input group "━━━━ ST顺势手数与加仓 ━━━━"
input double 顺势首仓手数 = 0.01; // ST趋势首仓基础手数
input double 顺势加仓手数递增比例 = 1.00; // ST顺势加仓递增比例(1.00=不按幂次放大)
input ENUM_ST_DISTANCE_MODE 顺势距离计算模式 = ST_DISTANCE_PERCENT; // 默认按价格百分比动态计算
input double 顺势回调百分比 = 0.05; // %;基准=当前趋势极值,4000时约2.00价格距离
input double 顺势恢复确认百分比 = 0.02; // %;基准=本次回调极值,4000时约0.80价格距离
input double 最小同向加仓间距百分比 = 0.04; // %;基准=最近一次同向成交价,4000时约1.60
input double 固定顺势回调距离 = 200; // 仅固定模式使用:参数点数
input double 固定顺势恢复确认距离 = 30; // 仅固定模式使用:参数点数
input double 固定最小同向加仓价格间距 = 1.50; // 仅固定模式使用:实际价格差
input int 顺势最大层数 = 60; // TREND首仓+加仓最大层数(保持60)
input int 顺势最小加仓间隔秒 = 60; // 防止剧烈行情连续加仓
input bool 启用最小同向加仓间距 = true; // 顺势/逆势加仓统一启用硬性价格间距保护
input group "━━━━ ST Basket退出 ━━━━"
input double ST篮子止盈金额 = 4.0; // 顺势+逆势同时存在时,所有仓统一净利润止盈(U)
input bool 逆势盈利允许独立退出 = true; // 逆势恢复组盈利后可单独退出,不必等待顺势组
input double 逆势独立退出净利润 = 0.0; // U;0=只要逆势整组净利润>0即可退出
input group "━━━━ V1.07原始逆势模式 ━━━━"
input double 原始初始手数 = 0.01; // 原始逆势模式首仓基础手数
input double 逆势加仓手数递增比例 = 1.09; // 原始逆势模式后续加仓手数递增比例
input double 加仓间距变更订单数 = 2; // 订单数超过此值使用二阶,否则使用一阶
input double 多单_挂单点距_非首单 = 200;
input double 多单_修改挂单起始间距 = 200;
input double 空单_挂单点距_非首单 = 200;
input double 空单_修改挂单起始间距 = 200;
input double 挂单点距_非首单_一阶 = 200;
input double 多单_修改挂单起始间距_一阶 = 200;
input double 挂单点距_非首单_二阶 = 120;
input double 修改挂单起始间距_二阶 = 120;
input int 首单止盈点数 = 150; // 原参数保留(当前核心交易未引用)
input double 单边止盈金额 = 50; // 原始模式单边止盈金额
input double 双边止盈金额 = 4; // 原始模式多空组合止盈金额
// 以下为原源码内部固定配置,不开放优化参数
string 交易设置= "* =============================== *";
string 风控= "* =============================== *";
bool 风控管理 = false; // 锁仓触发,停止开仓加仓!
double 浮亏锁仓金额 = -100;
double 止损金额= -300;
string 信息显示= "* =============================== *";
string dangerHours= "0"; // 开仓需要过滤的小时
bool by_bo_9 = true;
bool by_bo_10 = false;
int stop挂单改价间距 = 10;
double by_do_17 = -100000;
double 基准止盈金额 = 3; // 基准止盈金额
double by_do_19 = 0;
int 开仓手数小数位 = 2;
int 显示用_字体大小 = 9; // 字体大小
color by_ui_26 = clrAqua; // 字体颜色
// 可修改的变量(非input)
double 当前_多单_挂单点距_非首单;
double 当前_多单_修改挂单起始间距;
double 当前_空单_挂单点距_非首单;
double 当前_空单_修改挂单起始间距;
// 全局变量
double vPoint;
double vRawPoint;
double vPointFactor = 1.0;
double 市场当前最小挂单点距 = 0; // 兼容显示:当前新建挂单StopLevel(参数点)
double 市场当前StopLevel点距 = 0;
double 市场当前FreezeLevel点距 = 0;
bool runEA = true;
bool runEAG = false;
bool 手动全平 = true;
// 持仓统计变量
double 多单累计利润 = 0, 空单累计利润 = 0;
double 多单累计手数 = 0, 空单累计手数 = 0;
int 多单订单数 = 0, 空单订单数 = 0;
int 多单_挂单订单数 = 0, 空单_挂单订单数 = 0;
double 多单最高价 = 0, 多单最低价 = 0;
double 空单最高价 = 0, 空单最低价 = 0;
double 多单持仓均价 = 0, 空单持仓均价 = 0;
double _FirstStep;
string UI_PREFIX = "MD_UI_";
string UI_BG = "MD_UI_BG";
string LINE_BUY_AVG = "MD_LINE_BUY_AVG";
string LINE_SELL_AVG = "MD_LINE_SELL_AVG";
string LINE_BUY_TP = "MD_LINE_BUY_TP";
string LINE_SELL_TP = "MD_LINE_SELL_TP";
string LINE_BASKET_TP= "MD_LINE_BASKET_TP";
// 当前可视化价格
double g_buyTPPrice=0.0, g_sellTPPrice=0.0, g_basketTPPrice=0.0;
string g_tpModeText="无持仓";
// ===== 统计模块(券商服务器日期) =====
string STAT_UI_PREFIX = "MD_STATUI_";
bool g_statsPanelOpen = false;
datetime g_statsDay = 0;
double g_todayRealizedProfit = 0.0;
double g_todayOpenedLots = 0.0;
double g_todayMaxEntryLot = 0.0;
int g_todayCloseTrades = 0;
int g_todayWins = 0;
int g_todayLosses = 0;
double g_todayMaxFloatProfit = 0.0;
double g_todayMaxFloatLoss = 0.0;
double g_todayStartBalance = 0.0;
double g_todayLastEquity = 0.0;
datetime g_lastHistoryStatsCalc = 0;
datetime g_lastFloatStatSave = 0;
// 启动周期统计:每次EA重新启动时从0开始,不持久化
datetime g_sessionStartTime = 0;
double g_sessionStartBalance = 0.0;
double g_sessionMaxFloatLoss = 0.0;
// ===== ST顺势 + 逆势恢复运行状态 =====
int g_stAtrHandle = INVALID_HANDLE;
int g_stDir = 0; // 1=多 -1=空 0=未就绪
double g_stValue = 0.0;
datetime g_stLastBarTime = 0;
int g_stCandidateDir = 0;
int g_stCandidateBars = 0;
double g_trendExtremeBuy = 0.0; // 多头趋势峰值
double g_trendExtremeSell = 0.0; // 空头趋势谷值
bool g_buyPullbackArmed = false;
bool g_sellPullbackArmed = false;
double g_buyPullbackLow = 0.0;
double g_sellPullbackHigh = 0.0;
datetime g_lastTrendAddTime = 0;
// 顺势生命周期重启控制:某方向顺势仓从有仓变为0后,必须等下一根K线才可重启首仓
int g_prevTrendCountBuy = -1;
int g_prevTrendCountSell = -1;
datetime g_trendRestartBlockBarBuy = 0;
datetime g_trendRestartBlockBarSell = 0;
int g_todaySTFlips = 0; // 当日ST确认切换次数(券商日)
int g_todayRecoveryAdds = 0; // 当日逆势恢复加仓成功次数(券商日)
int g_recoveryDir = 0; // 当前逆势恢复方向: 1=BUY -1=SELL 0=无
double g_recoveryBaseLots = 0.0; // ST切换瞬间逆势方向总手数快照,作为“虚拟首仓”
int g_recoveryAddLevel = 0; // 当前快照之后已成交的逆势恢复加仓次数
datetime g_recoverySnapshotTime = 0; // 当前逆势基准快照时间
string ST_LINE_PREFIX = "MJ_ST_";
// 统计事件函数原型(实现位于每日统计模块)
void RecordSTFlipStat();
void RecordRecoveryAddStat();
//+------------------------------------------------------------------+
//| ST顺势 + 逆势恢复辅助函数 |
//+------------------------------------------------------------------+
double NormalizeBrokerVolume(double vol)
{
double vmin=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
double vmax=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
if(step<=0.0) step=0.01;
vol=MathMax(vmin,MathMin(vmax,vol));
vol=MathFloor(vol/step+1e-8)*step;
return NormalizeDouble(vol,8);
}
bool IsOurPositionComment(string key)
{
string c=PositionInfo.Comment();
return (StringFind(c,key)>=0);
}
// 获取当前仍持有的同方向仓位中,最近一次真实成交的开仓价格
bool GetLastSameDirectionEntry(int dir,double &lastPrice,datetime &lastTime,ulong &lastTicket)
{
lastPrice=0.0; lastTime=0; lastTicket=0;
for(int i=0;i< ositionsTotal();i++)
{
if(!PositionInfo.SelectByIndex(i)) continue;
if(PositionInfo.Symbol()!=_Symbol || PositionInfo.Magic()!=magicNumber) continue;
ENUM_POSITION_TYPE pt=PositionInfo.PositionType();
if(dir==1 && pt!=POSITION_TYPE_BUY) continue;
if(dir==-1 && pt!=POSITION_TYPE_SELL) continue;
datetime tm=PositionInfo.Time();
ulong tk=(ulong)PositionInfo.Ticket();
if(tm>lastTime || (tm==lastTime && tk>lastTicket))
{
lastTime=tm; lastTicket=tk; lastPrice=PositionInfo.PriceOpen();
}
}
return (lastPrice>0.0);
}
// 百分比转实际价格距离。参数按“百分数”填写:0.05 表示 0.05%。
double PercentToPriceDistance(double basePrice,double pct)
{
if(basePrice<=0.0 || pct<=0.0) return 0.0;
return basePrice*pct/100.0;
}
// 当前顺势回调所需的实际价格距离。百分比模式以趋势极值为基准。
double TrendPullbackDistancePrice(int dir,double currentPrice)
{
if(顺势距离计算模式==ST_DISTANCE_PERCENT)
{
double base=(dir==1?g_trendExtremeBuy:g_trendExtremeSell);
if(base<=0.0) base=currentPrice;
return PercentToPriceDistance(base,顺势回调百分比);
}
return 固定顺势回调距离*vPoint;
}
// 当前恢复确认所需的实际价格距离。百分比模式以本次回调最低/最高点为基准。
double TrendRecoveryConfirmDistancePrice(int dir,double currentPrice)
{
if(顺势距离计算模式==ST_DISTANCE_PERCENT)
{
double base=(dir==1?g_buyPullbackLow:g_sellPullbackHigh);
if(base<=0.0) base=currentPrice;
return PercentToPriceDistance(base,顺势恢复确认百分比);
}
return 固定顺势恢复确认距离*vPoint;
}
// 与最近一次同方向真实成交价之间必须保持的实际价格距离。
double MinSameDirectionSpacingPrice(double lastPrice)
{
if(!启用最小同向加仓间距) return 0.0;
if(顺势距离计算模式==ST_DISTANCE_PERCENT)
return PercentToPriceDistance(lastPrice,最小同向加仓间距百分比);
return MathMax(0.0,固定最小同向加仓价格间距);
}
// 统一加仓硬间距:首仓不受限;所有后续顺势/逆势加仓都使用相同的距离栅栏。
bool PassAddPriceSpacing(int dir,double candidatePrice,double &gapPrice,double &lastPrice)
{
gapPrice=0.0; lastPrice=0.0;
if(!启用最小同向加仓间距) return true;
datetime tm=0; ulong tk=0;
if(!GetLastSameDirectionEntry(dir,lastPrice,tm,tk)) return true; // 没有同向持仓=首仓,不限制
double minGap=MinSameDirectionSpacingPrice(lastPrice);
if(minGap<=0.0) return true;
gapPrice=MathAbs(candidatePrice-lastPrice);
double tick=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
if(tick<=0.0) tick=_Point;
return (gapPrice+tick*0.1 >= minGap);
}
int CountTrendPositions(int dir)
{
int n=0;
for(int i=0;i< ositionsTotal();i++)
{
if(!PositionInfo.SelectByIndex(i)) continue;
if(PositionInfo.Symbol()!=_Symbol || PositionInfo.Magic()!=magicNumber) continue;
if(dir==1 && PositionInfo.PositionType()!=POSITION_TYPE_BUY) continue;
if(dir==-1 && PositionInfo.PositionType()!=POSITION_TYPE_SELL) continue;
if(IsOurPositionComment("MJ_TREND")) n++;
}
return n;
}
double SumDirectionLots(int dir)
{
double v=0.0;
for(int i=0;i< ositionsTotal();i++)
{
if(!PositionInfo.SelectByIndex(i)) continue;
if(PositionInfo.Symbol()!=_Symbol || PositionInfo.Magic()!=magicNumber) continue;
if(dir==1 && PositionInfo.PositionType()==POSITION_TYPE_BUY) v+=PositionInfo.Volume();
if(dir==-1 && PositionInfo.PositionType()==POSITION_TYPE_SELL) v+=PositionInfo.Volume();
}
return v;
}
bool HasRecoveryPending(int dir)
{
for(int i=0;i<OrdersTotal();i++)
{
if(!OrderInfo.SelectByIndex(i)) continue;
if(OrderInfo.Symbol()!=_Symbol || OrderInfo.Magic()!=magicNumber) continue;
string c=OrderInfo.Comment();
if(StringFind(c,"MJ_RECOVERY")<0) continue;
if(dir==1 && OrderInfo.OrderType()==ORDER_TYPE_BUY_STOP) return true;
if(dir==-1 && OrderInfo.OrderType()==ORDER_TYPE_SELL_STOP) return true;
}
return false;
}
void DeleteRecoveryPending(int dir)
{
for(int i=OrdersTotal()-1;i>=0;i--)
{
if(!OrderInfo.SelectByIndex(i)) continue;
if(OrderInfo.Symbol()!=_Symbol || OrderInfo.Magic()!=magicNumber) continue;
string c=OrderInfo.Comment();
if(StringFind(c,"MJ_RECOVERY")<0) continue;
bool hit=(dir==1 && OrderInfo.OrderType()==ORDER_TYPE_BUY_STOP) ||
(dir==-1 && OrderInfo.OrderType()==ORDER_TYPE_SELL_STOP);
if(hit) Trade.OrderDelete(OrderInfo.Ticket());
}
}
bool HasTrendPending(int dir)
{
for(int i=0;i<OrdersTotal();i++)
{
if(!OrderInfo.SelectByIndex(i)) continue;
if(OrderInfo.Symbol()!=_Symbol || OrderInfo.Magic()!=magicNumber) continue;
if(dir==1 && OrderInfo.OrderType()==ORDER_TYPE_BUY_STOP && StringFind(OrderInfo.Comment(),"MJ_TREND")>=0) return true;
if(dir==-1 && OrderInfo.OrderType()==ORDER_TYPE_SELL_STOP && StringFind(OrderInfo.Comment(),"MJ_TREND")>=0) return true;
}
return false;
}
void DeleteTrendPending(int dir)
{
for(int i=OrdersTotal()-1;i>=0;i--)
{
if(!OrderInfo.SelectByIndex(i)) continue;
if(OrderInfo.Symbol()!=_Symbol || OrderInfo.Magic()!=magicNumber) continue;
string c=OrderInfo.Comment();
if(StringFind(c,"MJ_TREND")<0) continue;
bool hit=(dir==1 && OrderInfo.OrderType()==ORDER_TYPE_BUY_STOP) ||
(dir==-1 && OrderInfo.OrderType()==ORDER_TYPE_SELL_STOP);
if(hit) Trade.OrderDelete(OrderInfo.Ticket());
}
}
void ResetTrendTrackerForDir(int dir)
{
if(dir==1)
{
g_buyPullbackArmed=false;
g_buyPullbackLow=0.0;
g_trendExtremeBuy=0.0;
}
else if(dir==-1)
{
g_sellPullbackArmed=false;
g_sellPullbackHigh=0.0;
g_trendExtremeSell=0.0;
}
}
// 检测“该方向顺势仓刚刚全部退出”。只在真正从 >0 变成 0 时启动下一K线重启门控,
// EA刚启动时本来就是0仓不会被误判,从而保持原来的首次启动行为。
void UpdateTrendCycleRestartState()
{
int buyCount=CountTrendPositions(1);
int sellCount=CountTrendPositions(-1);
datetime curBar=iTime(_Symbol,_Period,0);
if(g_prevTrendCountBuy>=0 && g_prevTrendCountBuy>0 && buyCount==0)
{
DeleteTrendPending(1);
ResetTrendTrackerForDir(1);
g_trendRestartBlockBarBuy=curBar;
Print("【顺势周期完成】BUY顺势仓已全部退出,本K线不重开;下一根K线若ST仍为多则允许重启首仓");
}
if(g_prevTrendCountSell>=0 && g_prevTrendCountSell>0 && sellCount==0)
{
DeleteTrendPending(-1);
ResetTrendTrackerForDir(-1);
g_trendRestartBlockBarSell=curBar;
Print("【顺势周期完成】SELL顺势仓已全部退出,本K线不重开;下一根K线若ST仍为空则允许重启首仓");
}
g_prevTrendCountBuy=buyCount;
g_prevTrendCountSell=sellCount;
}
bool TrendRestartAllowedNow(int dir)
{
datetime curBar=iTime(_Symbol,_Period,0);
datetime blockBar=(dir==1?g_trendRestartBlockBarBuy:g_trendRestartBlockBarSell);
if(blockBar<=0) return true;
if(curBar<=blockBar) return false;
// 已进入下一根K线:解除门控,并从当前价格重新建立这一轮趋势追踪基准。
if(dir==1) g_trendRestartBlockBarBuy=0;
else g_trendRestartBlockBarSell=0;
ResetTrendTrackerForDir(dir);
Print("【顺势周期重启】",(dir==1?"BUY":"SELL")," 已进入下一根K线,允许重新建立首仓");
return true;
}
void DeleteDirectionPending(int dir)
{
for(int i=OrdersTotal()-1;i>=0;i--)
{
if(!OrderInfo.SelectByIndex(i)) continue;
if(OrderInfo.Symbol()!=_Symbol || OrderInfo.Magic()!=magicNumber) continue;
bool hit=(dir==1 && OrderInfo.OrderType()==ORDER_TYPE_BUY_STOP) || (dir==-1 && OrderInfo.OrderType()==ORDER_TYPE_SELL_STOP);
if(hit) Trade.OrderDelete(OrderInfo.Ticket());
}
}
bool GetSupertrendClosed(int shift,int &direction,double &value)
{
direction=0; value=0.0;
if(g_stAtrHandle==INVALID_HANDLE || shift<1) return false;
int need=MathMax(Supertrend_ATR周期*6+shift+10,140);
MqlRates rates[]; double atr[];
ArraySetAsSeries(rates,true); ArraySetAsSeries(atr,true);
int rc=CopyRates(_Symbol,_Period,0,need,rates);
int ac=CopyBuffer(g_stAtrHandle,0,0,need,atr);
int n=MathMin(rc,ac);
if(n<Supertrend_ATR周期+shift+10) return false;
int oldest=n-1;
double mid=(rates[oldest].high+rates[oldest].low)*0.5;
double fu=mid+Supertrend_倍数*atr[oldest];
double fl=mid-Supertrend_倍数*atr[oldest];
int trend=(rates[oldest].close>=mid?1:-1);
double st=(trend==1?fl:fu);
for(int i=oldest-1;i>=shift;i--)
{
mid=(rates.high+rates.low)*0.5;
double bu=mid+Supertrend_倍数*atr;
double bl=mid-Supertrend_倍数*atr;
double pc=rates[i+1].close;
double nu=(bu<fu || pc>fu)?bu:fu;
double nl=(bl>fl || pc<fl)?bl:fl;
if(rates.close>fu) trend=1;
else if(rates.close<fl) trend=-1;
fu=nu; fl=nl; st=(trend==1?fl:fu);
}
direction=trend; value=st;
return true;
}
void DeleteSTLines()
{
ObjectsDeleteAll(0,ST_LINE_PREFIX);
}
void DrawSupertrendLines()
{
if(!显示Supertrend || g_stAtrHandle==INVALID_HANDLE) { DeleteSTLines(); return; }
int need=180;
MqlRates rates[]; double atr[];
ArraySetAsSeries(rates,true); ArraySetAsSeries(atr,true);
int rc=CopyRates(_Symbol,_Period,0,need,rates);
int ac=CopyBuffer(g_stAtrHandle,0,0,need,atr);
int n=MathMin(rc,ac);
if(n<Supertrend_ATR周期+20) return;
double st[]; int dirs[];
ArrayResize(st,n); ArrayResize(dirs,n);
int oldest=n-1;
double mid=(rates[oldest].high+rates[oldest].low)*0.5;
double fu=mid+Supertrend_倍数*atr[oldest];
double fl=mid-Supertrend_倍数*atr[oldest];
int trend=(rates[oldest].close>=mid?1:-1);
st[oldest]=(trend==1?fl:fu); dirs[oldest]=trend;
for(int i=oldest-1;i>=1;i--)
{
mid=(rates.high+rates.low)*0.5;
double bu=mid+Supertrend_倍数*atr;
double bl=mid-Supertrend_倍数*atr;
double pc=rates[i+1].close;
double nu=(bu<fu || pc>fu)?bu:fu;
double nl=(bl>fl || pc<fl)?bl:fl;
if(rates.close>fu) trend=1;
else if(rates.close<fl) trend=-1;
fu=nu; fl=nl; st=(trend==1?fl:fu); dirs=trend;
}
DeleteSTLines();
int maxDraw=MathMin(120,n-2);
for(int i=maxDraw;i>=2;i--)
{
string name=ST_LINE_PREFIX+IntegerToString(i);
if(ObjectCreate(0,name,OBJ_TREND,0,rates.time,st,rates[i-1].time,st[i-1]))
{
ObjectSetInteger(0,name,OBJPROP_COLOR,(dirs[i-1]==1?clrLime:clrTomato));
ObjectSetInteger(0,name,OBJPROP_WIDTH,2);
ObjectSetInteger(0,name,OBJPROP_RAY_LEFT,false);
ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,false);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
}
}
}
bool OpenMarketTagged(int dir,double volume,string tag)
{
volume=NormalizeBrokerVolume(volume);
if(volume<=0.0) return false;
Trade.SetExpertMagicNumber(magicNumber);
bool ok=(dir==1 ? Trade.Buy(volume,_Symbol,0,0,0,tag) : Trade.Sell(volume,_Symbol,0,0,0,tag));
if(ok) Print("【ST引擎】",tag," 成交 ",DoubleToString(volume,2)," Lot");
else Print("【ST引擎失败】",tag," retcode=",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
return ok;
}
void TryOpenTrendFirst(int dir)
{
if(dir==0 || !runEA) return;
if(dir==1 && !allow_buy) return;
if(dir==-1 && !allow_sell) return;
if(CountTrendPositions(dir)>0 || HasTrendPending(dir)) return;
// 上一轮同方向顺势仓刚清空时,不允许同K线立即重开;必须等待下一根K线。
if(!TrendRestartAllowedNow(dir)) return;
int currentHour=GetCurrentHour();
if(sj && !(currentHour>star && currentHour<end)) return;
if(isInDangerTime(dangerHours)) return;
double p=(dir==1 ? SymbolInfoDouble(_Symbol,SYMBOL_ASK)+_FirstStep*vPoint
: SymbolInfoDouble(_Symbol,SYMBOL_BID)-_FirstStep*vPoint);
ENUM_ORDER_TYPE type=(dir==1?ORDER_TYPE_BUY_STOP:ORDER_TYPE_SELL_STOP);
p=修正新建挂单价格(type,NormalizeDouble(p,_Digits));
double v=NormalizeBrokerVolume(顺势首仓手数*开仓倍数);
MqlTradeRequest req={}; MqlTradeResult res={};
req.action=TRADE_ACTION_PENDING; req.symbol=_Symbol; req.volume=v; req.price=p;
req.type=type; req.magic=magicNumber;
req.comment=(dir==1?"MJ_TREND_FIRST_BUY":"MJ_TREND_FIRST_SELL");
if(OrderSend(req,res) && 交易返回成功(res.retcode))
Print("【ST首仓挂单】",(dir==1?"BUY":"SELL")," ",DoubleToString(v,2)," @ ",DoubleToString(p,_Digits));
else
Print("【ST首仓挂单失败】retcode=",res.retcode," ",res.comment);
}
void ResetTrendTracker(int allowedDir)
{
g_buyPullbackArmed=false; g_sellPullbackArmed=false;
g_buyPullbackLow=0.0; g_sellPullbackHigh=0.0;
if(allowedDir==1)
{
g_trendExtremeBuy=SymbolInfoDouble(_Symbol,SYMBOL_BID);
g_trendExtremeSell=0.0;
}
else if(allowedDir==-1)
{
g_trendExtremeSell=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
g_trendExtremeBuy=0.0;
}
}
string RecoveryStatePrefix()
{
return "MJREC_"+IntegerToString((int)AccountInfoInteger(ACCOUNT_LOGIN))+"_"+_Symbol+"_"+IntegerToString((int)magicNumber)+"_";
}
void SaveRecoveryState()
{
string p=RecoveryStatePrefix();
GlobalVariableSet(p+"DIR",(double)g_recoveryDir);
GlobalVariableSet(p+"BASE",g_recoveryBaseLots);
GlobalVariableSet(p+"LEVEL",(double)g_recoveryAddLevel);
GlobalVariableSet(p+"TIME",(double)g_recoverySnapshotTime);
}
void ClearRecoveryState()
{
g_recoveryDir=0;
g_recoveryBaseLots=0.0;
g_recoveryAddLevel=0;
g_recoverySnapshotTime=0;
SaveRecoveryState();
}
void CaptureRecoverySnapshot(int dir)
{
double lots=(dir==0?0.0:SumDirectionLots(dir));
if(dir==0 || lots<=0.0)
{
ClearRecoveryState();
return;
}
g_recoveryDir=dir;
g_recoveryBaseLots=NormalizeBrokerVolume(lots);
g_recoveryAddLevel=0;
g_recoverySnapshotTime=TimeCurrent();
SaveRecoveryState();
Print("【逆势基准快照】方向=",(dir==1?"BUY":"SELL"),
" 基准总手数=",DoubleToString(g_recoveryBaseLots,2),
",后续按此虚拟首仓×逆势倍数计算");
}
bool LoadRecoveryStateForCurrentTrend()
{
string p=RecoveryStatePrefix();
if(!GlobalVariableCheck(p+"DIR") || !GlobalVariableCheck(p+"BASE")) return false;
int dir=(int)GlobalVariableGet(p+"DIR");
double base=GlobalVariableGet(p+"BASE");
int level=(GlobalVariableCheck(p+"LEVEL")?(int)GlobalVariableGet(p+"LEVEL"):0);
datetime tm=(GlobalVariableCheck(p+"TIME")?(datetime)GlobalVariableGet(p+"TIME"):0);
if(dir==0 || base<=0.0 || dir!=-g_stDir || SumDirectionLots(dir)<=0.0) return false;
g_recoveryDir=dir; g_recoveryBaseLots=base; g_recoveryAddLevel=MathMax(0,level); g_recoverySnapshotTime=tm;
return true;
}
int RecoveryVirtualOrderCount()
{
if(g_recoveryDir==0 || g_recoveryBaseLots<=0.0) return 0;
return 1+MathMax(0,g_recoveryAddLevel); // 快照总量视为1个虚拟首仓
}
void GetRecoveryDistances(double &pendingDist,double &modifyDist)
{
int virtualCount=RecoveryVirtualOrderCount();
if(virtualCount>加仓间距变更订单数)
{
pendingDist=挂单点距_非首单_二阶;
modifyDist=修改挂单起始间距_二阶;
}
else
{
pendingDist=挂单点距_非首单_一阶;
modifyDist=多单_修改挂单起始间距_一阶;
}
}
double RecoveryNextLot()
{
if(g_recoveryBaseLots<=0.0) return 0.0;
int nextLevel=MathMax(1,g_recoveryAddLevel+1);
double lot=g_recoveryBaseLots*MathPow(逆势加仓手数递增比例,nextLevel);
// 保留原源码线性附加项语义,但以虚拟层级计数,默认by_do_19=0不产生影响
lot += nextLevel*by_do_19;
return NormalizeBrokerVolume(lot);
}
void HandleConfirmedSTFlip(int oldDir,int newDir)
{
Print("【ST趋势切换】",(oldDir==1?"多":"空")," -> ",(newDir==1?"多":"空"));
RecordSTFlipStat();
// 新趋势方向允许开新仓;旧趋势方向立即转为“逆势恢复组”。
// 先删除旧方向遗留挂单,再把切换瞬间旧方向全部持仓总量视为一个虚拟首仓快照。
DeleteDirectionPending(oldDir);
CaptureRecoverySnapshot(oldDir);
// ST真正翻转本身就是一个全新的趋势生命周期,不继承该方向旧周期的“等下一根K线”门控。
if(newDir==1) g_trendRestartBlockBarBuy=0;
else if(newDir==-1) g_trendRestartBlockBarSell=0;
ResetTrendTracker(newDir);
}
void UpdateSTDirectionOnClosedBar()
{
datetime bar=iTime(_Symbol,_Period,0);
if(bar<=0 || bar==g_stLastBarTime) return;
g_stLastBarTime=bar;
int raw=0; double val=0.0;
if(!GetSupertrendClosed(1,raw,val)) return;
g_stValue=val;
if(raw==g_stCandidateDir) g_stCandidateBars++;
else { g_stCandidateDir=raw; g_stCandidateBars=1; }
int need=MathMax(1,MathMin(3,ST趋势确认K线数));
if(g_stCandidateBars<need) return;
if(g_stDir==0)
{
g_stDir=raw;
ResetTrendTracker(g_stDir);
if(!LoadRecoveryStateForCurrentTrend())
CaptureRecoverySnapshot(-g_stDir);
Print("【ST初始化】当前趋势=",(g_stDir==1?"多":"空"));
}
else if(raw!=g_stDir)
{
int old=g_stDir; g_stDir=raw;
HandleConfirmedSTFlip(old,g_stDir);
}
DrawSupertrendLines();
}
void ProcessTrendPullbackAdd()
{
if(!启用ST顺势逆势恢复模式 || g_stDir==0 || !runEA) return;
int trendCount=CountTrendPositions(g_stDir);
if(trendCount<=0) { TryOpenTrendFirst(g_stDir); return; }
if(trendCount>=顺势最大层数) return;
if(g_lastTrendAddTime>0 && TimeCurrent()-g_lastTrendAddTime<顺势最小加仓间隔秒) return;
double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double pull=TrendPullbackDistancePrice(g_stDir,(g_stDir==1?bid:ask));
double confirm=TrendRecoveryConfirmDistancePrice(g_stDir,(g_stDir==1?bid:ask));
if(g_stDir==1)
{
if(g_trendExtremeBuy<=0.0 || bid>g_trendExtremeBuy)
{
g_trendExtremeBuy=bid;
if(!g_buyPullbackArmed) g_buyPullbackLow=0.0;
}
if(!g_buyPullbackArmed && g_trendExtremeBuy-bid>=pull)
{
g_buyPullbackArmed=true; g_buyPullbackLow=bid;
Print("【顺势多回调】已达到回调距离,开始等待恢复确认");
}
if(g_buyPullbackArmed)
{
if(g_buyPullbackLow<=0.0 || bid<g_buyPullbackLow) g_buyPullbackLow=bid;
if(bid-g_buyPullbackLow>=TrendRecoveryConfirmDistancePrice(1,bid))
{
double gap=0.0,lastPrice=0.0;
double candidate=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
if(!PassAddPriceSpacing(1,candidate,gap,lastPrice))
{
Print("【顺势多加仓拦截】与上一笔BUY间距=",DoubleToString(gap,_Digits),
" < 最小间距=",DoubleToString(MinSameDirectionSpacingPrice(lastPrice),_Digits),
" 上一笔=",DoubleToString(lastPrice,_Digits));
return;
}
double v=NormalizeDouble(trendCount*by_do_19+顺势首仓手数*MathPow(顺势加仓手数递增比例,trendCount),开仓手数小数位)*开仓倍数;
if(OpenMarketTagged(1,v,"MJ_TREND_ADD_BUY"))
{
g_lastTrendAddTime=TimeCurrent();
g_buyPullbackArmed=false; g_buyPullbackLow=0.0; g_trendExtremeBuy=bid;
}
}
}
}
else
{
if(g_trendExtremeSell<=0.0 || ask<g_trendExtremeSell)
{
g_trendExtremeSell=ask;
if(!g_sellPullbackArmed) g_sellPullbackHigh=0.0;
}
if(!g_sellPullbackArmed && ask-g_trendExtremeSell>=pull)
{
g_sellPullbackArmed=true; g_sellPullbackHigh=ask;
Print("【顺势空回调】已达到反弹距离,开始等待恢复确认");
}
if(g_sellPullbackArmed)
{
if(g_sellPullbackHigh<=0.0 || ask>g_sellPullbackHigh) g_sellPullbackHigh=ask;
if(g_sellPullbackHigh-ask>=TrendRecoveryConfirmDistancePrice(-1,ask))
{
double gap=0.0,lastPrice=0.0;
double candidate=SymbolInfoDouble(_Symbol,SYMBOL_BID);
if(!PassAddPriceSpacing(-1,candidate,gap,lastPrice))
{
Print("【顺势空加仓拦截】与上一笔SELL间距=",DoubleToString(gap,_Digits),
" < 最小间距=",DoubleToString(MinSameDirectionSpacingPrice(lastPrice),_Digits),
" 上一笔=",DoubleToString(lastPrice,_Digits));
return;
}
double v=NormalizeDouble(trendCount*by_do_19+顺势首仓手数*MathPow(顺势加仓手数递增比例,trendCount),开仓手数小数位)*开仓倍数;
if(OpenMarketTagged(-1,v,"MJ_TREND_ADD_SELL"))
{
g_lastTrendAddTime=TimeCurrent();
g_sellPullbackArmed=false; g_sellPullbackHigh=0.0; g_trendExtremeSell=ask;
}
}
}
}
}
//+------------------------------------------------------------------+
//| ST模式下:反趋势方向只允许在已有持仓基础上按V1.07规则继续加仓 |
//| 严禁在反趋势方向从0开始建立新仓 |
//+------------------------------------------------------------------+
bool PlaceRecoveryPending(int dir)
{
if(dir==0 || !runEA) return false;
if(dir==1 && !allow_buy) return false;
if(dir==-1 && !allow_sell) return false;
int posCount=(dir==1?多单订单数:空单订单数);
if(posCount<=0) return false; // 核心约束:逆势绝不新开首仓
if(dir!=g_recoveryDir || g_recoveryBaseLots<=0.0) return false;
if(HasRecoveryPending(dir)) return false;
int currentHour=GetCurrentHour();
if(sj && !(currentHour>star && currentHour<end)) return false;
double trade_price=0.0;
double pendingDist=0.0, modifyDist=0.0;
GetRecoveryDistances(pendingDist,modifyDist);
if(dir==1)
{
trade_price=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK)+pendingDist*vPoint,_Digits);
bool allowed=((多单最高价!=0 && trade_price>=NormalizeDouble(多单最高价+modifyDist*vPoint,_Digits) && by_bo_10) ||
(多单最低价!=0 && trade_price<=NormalizeDouble(多单最低价-modifyDist*vPoint,_Digits)));
if(!allowed) return false;
}
else
{
trade_price=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID)-pendingDist*vPoint,_Digits);
bool allowed=((空单最低价!=0 && trade_price<=NormalizeDouble(空单最低价-modifyDist*vPoint,_Digits) && by_bo_10) ||
(空单最高价!=0 && trade_price>=NormalizeDouble(空单最高价+modifyDist*vPoint,_Digits)));
if(!allowed) return false;
}
double trade_lot=RecoveryNextLot();
if(!((trade_lot < AccountInfoDouble(ACCOUNT_FREEMARGIN)/SymbolInfoDouble(_Symbol,SYMBOL_TRADE_CONTRACT_SIZE) && posCount>0) || by_bo_9))
return false;
ENUM_ORDER_TYPE type=(dir==1?ORDER_TYPE_BUY_STOP:ORDER_TYPE_SELL_STOP);
trade_price=修正新建挂单价格(type,trade_price);
double gap=0.0,lastPrice=0.0;
if(!PassAddPriceSpacing(dir,trade_price,gap,lastPrice))
{
Print("【逆势恢复挂单拦截】",(dir==1?"BUY":"SELL")," 候选价=",DoubleToString(trade_price,_Digits),
" 与上一笔间距=",DoubleToString(gap,_Digits)," < 最小间距=",
DoubleToString(MinSameDirectionSpacingPrice(lastPrice),_Digits));
return false;
}
MqlTradeRequest request={}; MqlTradeResult result={};
request.action=TRADE_ACTION_PENDING;
request.symbol=_Symbol; request.volume=trade_lot; request.price=trade_price;
request.sl=0; request.tp=0; request.type=type; request.magic=magicNumber;
request.comment=(dir==1?"MJ_RECOVERY_BUY":"MJ_RECOVERY_SELL");
ResetLastError();
bool sent=OrderSend(request,result);
if(sent && 交易返回成功(result.retcode))
{
Print("【逆势恢复挂单】",(dir==1?"BUY":"SELL")," 虚拟层=",RecoveryVirtualOrderCount(),
" 基准=",DoubleToString(g_recoveryBaseLots,2)," 下单手数=",DoubleToString(trade_lot,2),
" @ ",DoubleToString(trade_price,_Digits));
return true;
}
Print("【逆势恢复挂单失败】sent=",sent," retcode=",result.retcode," ",result.comment," err=",GetLastError());
return false;
}
void MoveRecoveryPending(int dir)
{
int posCount=(dir==1?多单订单数:空单订单数);
if(posCount<=0) { DeleteRecoveryPending(dir); return; }
for(int i=OrdersTotal()-1;i>=0;i--)
{
if(!OrderInfo.SelectByIndex(i)) continue;
if(OrderInfo.Symbol()!=_Symbol || OrderInfo.Magic()!=magicNumber) continue;
string c=OrderInfo.Comment();
if(StringFind(c,"MJ_RECOVERY")<0) continue;
ENUM_ORDER_TYPE typ=OrderInfo.OrderType();
if(dir==1 && typ!=ORDER_TYPE_BUY_STOP) continue;
if(dir==-1 && typ!=ORDER_TYPE_SELL_STOP) continue;
double currentPrice=OrderInfo.PriceOpen();
double existingGap=0.0,existingLast=0.0;
if(!PassAddPriceSpacing(dir,currentPrice,existingGap,existingLast))
{
ulong badTicket=OrderInfo.Ticket();
if(Trade.OrderDelete(badTicket))
Print("【逆势恢复挂单撤销】ticket=",badTicket," 与上一笔同向成交间距不足 ",
DoubleToString(existingGap,_Digits)," < ",DoubleToString(MinSameDirectionSpacingPrice(existingLast),_Digits));
continue;
}
double newPrice=0.0;
bool shouldModify=false;
double pendingDist=0.0, modifyDist=0.0;
GetRecoveryDistances(pendingDist,modifyDist);
if(dir==1)
{
newPrice=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK)+pendingDist*vPoint,_Digits);
if(NormalizeDouble(currentPrice-stop挂单改价间距*vPoint,_Digits)>newPrice &&
(newPrice<=NormalizeDouble(多单最低价-modifyDist*vPoint,_Digits) || 多单最低价==0 || by_bo_10) &&
(newPrice>=NormalizeDouble(modifyDist*vPoint+多单最高价,_Digits) ||
newPrice<=NormalizeDouble(多单最低价-modifyDist*vPoint,_Digits)))
shouldModify=true;
}
else
{
newPrice=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID)-pendingDist*vPoint,_Digits);
if(NormalizeDouble(currentPrice+stop挂单改价间距*vPoint,_Digits)<newPrice &&
(newPrice>=NormalizeDouble(modifyDist*vPoint+空单最高价,_Digits) || 空单最高价==0 || by_bo_10) &&
(newPrice<=NormalizeDouble(空单最低价-modifyDist*vPoint,_Digits) ||
newPrice>=NormalizeDouble(modifyDist*vPoint+空单最高价,_Digits)))
shouldModify=true;
}
if(!shouldModify || newPrice<=0) continue;
double legalPrice=newPrice;
if(!准备修改挂单价格(typ,currentPrice,newPrice,legalPrice)) continue;
double moveGap=0.0,moveLast=0.0;
if(!PassAddPriceSpacing(dir,legalPrice,moveGap,moveLast)) continue;
MqlTradeRequest req={}; MqlTradeResult res={};
req.action=TRADE_ACTION_MODIFY; req.order=OrderInfo.Ticket(); req.price=legalPrice;
req.sl=OrderInfo.StopLoss(); req.tp=OrderInfo.TakeProfit();
if(!OrderSend(req,res) || !交易返回成功(res.retcode))
Print("【逆势恢复改单失败】ticket=",OrderInfo.Ticket()," retcode=",res.retcode," ",res.comment);
}
}
void ProcessInverseRecoveryAdd()
{
if(!启用ST顺势逆势恢复模式 || g_stDir==0 || !runEA) return;
int inverseDir=-g_stDir;
int inverseCount=(inverseDir==1?多单订单数:空单订单数);
// 反趋势方向没有旧仓:删除残留恢复挂单,清除本轮基准;绝不建立新首仓。
if(inverseCount<=0)
{
DeleteRecoveryPending(inverseDir);
if(g_recoveryDir==inverseDir) ClearRecoveryState();
return;
}
// 兼容EA重启/状态缺失:当前反趋势持仓总量只在没有有效快照时抓取一次。
if(g_recoveryDir!=inverseDir || g_recoveryBaseLots<=0.0)
CaptureRecoverySnapshot(inverseDir);
PlaceRecoveryPending(inverseDir);
MoveRecoveryPending(inverseDir);
}
//+------------------------------------------------------------------+
//| 统计模块:券商服务器自然日 |
//+------------------------------------------------------------------+
datetime BrokerDayStart(datetime t)
{
MqlDateTime dt;
TimeToStruct(t,dt);
dt.hour=0; dt.min=0; dt.sec=0;
return StructToTime(dt);
}
int SymbolStatHash()
{
uint h=2166136261;
for(int i=0;i<StringLen(_Symbol);i++)
{
h ^= (uint)StringGetCharacter(_Symbol,i);
h *= 16777619;
}
return (int)(h & 0x7FFFFFFF);
}
string DailyStatPrefix(datetime day)
{
MqlDateTime dt; TimeToStruct(day,dt);
return "MDSTAT_"+IntegerToString((int)AccountInfoInteger(ACCOUNT_LOGIN))+"_"
+IntegerToString((int)magicNumber)+"_"+IntegerToString(SymbolStatHash())+"_"
+StringFormat("%04d%02d%02d_",dt.year,dt.mon,dt.day);
}
void LoadTodaySTEventStats(datetime day)
{
string k=DailyStatPrefix(day);
g_todaySTFlips=(GlobalVariableCheck(k+"stFlips")?(int)GlobalVariableGet(k+"stFlips"):0);
g_todayRecoveryAdds=(GlobalVariableCheck(k+"recoveryCount")?(int)GlobalVariableGet(k+"recoveryCount"):0);
}
void RecordSTFlipStat()
{
datetime day=BrokerDayStart(TimeCurrent());
string k=DailyStatPrefix(day);
double v=GlobalVariableCheck(k+"stFlips")?GlobalVariableGet(k+"stFlips"):0.0;
v+=1.0;
GlobalVariableSet(k+"stFlips",v);
if(g_statsDay==day) g_todaySTFlips=(int)v;
}
void RecordRecoveryAddStat()
{
datetime day=BrokerDayStart(TimeCurrent());
string k=DailyStatPrefix(day);
double v=GlobalVariableCheck(k+"recoveryCount")?GlobalVariableGet(k+"recoveryCount"):0.0;
v+=1.0;
GlobalVariableSet(k+"recoveryCount",v);
if(g_statsDay==day) g_todayRecoveryAdds=(int)v;
}
void DeleteStatsPanelObjects()
{
ObjectsDeleteAll(0,STAT_UI_PREFIX);
}
// 从历史成交重建某一天的“已实现类”统计。
// 只统计当前品种 + 当前Magic;盈亏包含 profit/swap/commission/fee。
bool CalcDailyHistoryStats(datetime day,double &pnl,double &openedLots,double &maxEntryLot,
int &closeTrades,int &wins,int &losses)
{
pnl=0.0; openedLots=0.0; maxEntryLot=0.0;
closeTrades=0; wins=0; losses=0;
datetime from=BrokerDayStart(day);
datetime now=TimeCurrent();
datetime to=from+86399;
if(BrokerDayStart(now)==from) to=now;
if(!HistorySelect(from,to)) return false;
int total=HistoryDealsTotal();
for(int i=0;i<total;i++)
{
ulong ticket=HistoryDealGetTicket(i);
if(ticket==0) continue;
if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol) continue;
if((long)HistoryDealGetInteger(ticket,DEAL_MAGIC)!=(long)magicNumber) continue;
double dealPnl=HistoryDealGetDouble(ticket,DEAL_PROFIT)
+HistoryDealGetDouble(ticket,DEAL_SWAP)
+HistoryDealGetDouble(ticket,DEAL_COMMISSION)
+HistoryDealGetDouble(ticket,DEAL_FEE);
pnl += dealPnl;
long entry=HistoryDealGetInteger(ticket,DEAL_ENTRY);
double vol=HistoryDealGetDouble(ticket,DEAL_VOLUME);
if(entry==DEAL_ENTRY_IN || entry==DEAL_ENTRY_INOUT)
{
openedLots += vol;
if(vol>maxEntryLot) maxEntryLot=vol;
}
if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY || entry==DEAL_ENTRY_INOUT)
{
closeTrades++;
if(dealPnl>0.0000001) wins++;
else if(dealPnl<-0.0000001) losses++;
}
}
return true;
}
void SaveDailyFloatStats(datetime day)
{
if(day<=0) return;
string k=DailyStatPrefix(day);
GlobalVariableSet(k+"maxFloat",g_todayMaxFloatProfit);
GlobalVariableSet(k+"minFloat",g_todayMaxFloatLoss);
GlobalVariableSet(k+"startBal",g_todayStartBalance);
GlobalVariableSet(k+"lastEq",g_todayLastEquity);
}
void LoadDailyFloatStats(datetime day)
{
string k=DailyStatPrefix(day);
g_todayMaxFloatProfit=GlobalVariableCheck(k+"maxFloat")?GlobalVariableGet(k+"maxFloat"):0.0;
g_todayMaxFloatLoss=GlobalVariableCheck(k+"minFloat")?GlobalVariableGet(k+"minFloat"):0.0;
g_todayStartBalance=GlobalVariableCheck(k+"startBal")?GlobalVariableGet(k+"startBal"):AccountInfoDouble(ACCOUNT_BALANCE);
g_todayLastEquity=GlobalVariableCheck(k+"lastEq")?GlobalVariableGet(k+"lastEq"):AccountInfoDouble(ACCOUNT_EQUITY);
}
void RefreshTodayHistoryStats(bool force=false)
{
datetime now=TimeCurrent();
datetime day=BrokerDayStart(now);
if(g_statsDay==0)
{
g_statsDay=day;
LoadDailyFloatStats(day);
LoadTodaySTEventStats(day);
}
else if(day!=g_statsDay)
{
// 券商服务器跨日:先保存旧日极值,再切换到新日
SaveDailyFloatStats(g_statsDay);
g_statsDay=day;
g_todayRealizedProfit=0.0;
g_todayOpenedLots=0.0;
g_todayMaxEntryLot=0.0;
g_todayCloseTrades=0;
g_todayWins=0;
g_todayLosses=0;
g_todaySTFlips=0;
g_todayRecoveryAdds=0;
LoadDailyFloatStats(day);
LoadTodaySTEventStats(day);
Print("【统计】券商服务器跨日,开始新一天: ",TimeToString(day,TIME_DATE));
force=true;
}
// 历史扫描最多每秒一次,避免高频tick反复扫描历史库
if(!force && g_lastHistoryStatsCalc==now) return;
g_lastHistoryStatsCalc=now;
CalcDailyHistoryStats(day,g_todayRealizedProfit,g_todayOpenedLots,g_todayMaxEntryLot,
g_todayCloseTrades,g_todayWins,g_todayLosses);
}
void UpdateFloatingStats()
{
double fp=多单累计利润+空单累计利润;
bool extremeChanged=false;
if(fp>g_todayMaxFloatProfit) { g_todayMaxFloatProfit=fp; extremeChanged=true; }
if(fp<g_todayMaxFloatLoss) { g_todayMaxFloatLoss=fp; extremeChanged=true; }
if(fp<g_sessionMaxFloatLoss) g_sessionMaxFloatLoss=fp;
g_todayLastEquity=AccountInfoDouble(ACCOUNT_EQUITY);
datetime now=TimeCurrent();
if(extremeChanged || g_lastFloatStatSave==0 || now-g_lastFloatStatSave>=30)
{
SaveDailyFloatStats(g_statsDay);
g_lastFloatStatSave=now;
}
}
//+------------------------------------------------------------------+
//| 获取当前时间的小时数 |
//+------------------------------------------------------------------+
// 参数点数统一按2位/4位报价习惯:
// 2位/3位品种:参数100点都代表1.00价格
// 4位/5位品种:参数100点都代表0.0100价格
void 刷新券商距离限制()
{
long stopLevelRaw = (long)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
long freezeLevelRaw = (long)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
市场当前StopLevel点距 = MathCeil((double)stopLevelRaw / vPointFactor);
市场当前FreezeLevel点距 = MathCeil((double)freezeLevelRaw / vPointFactor);
// 保留旧变量名供兼容/显示,但新建挂单只按StopLevel约束
市场当前最小挂单点距 = 市场当前StopLevel点距;
}
void 初始化点数自适应()
{
vRawPoint = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(vRawPoint <= 0.0)
vRawPoint = Point();
if(_Digits == 3 || _Digits == 5)
vPointFactor = 10.0;
else
vPointFactor = 1.0;
vPoint = vRawPoint * vPointFactor;
刷新券商距离限制();
long stopLevelRaw = (long)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
long freezeLevelRaw = (long)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
Print("【点数自适应】", _Symbol,
" Digits=", _Digits,
" RawPoint=", DoubleToString(vRawPoint, _Digits),
" 参数1点价格=", DoubleToString(vPoint, _Digits),
" 倍数=", DoubleToString(vPointFactor, 0),
" BrokerStop=", stopLevelRaw,
" BrokerFreeze=", freezeLevelRaw,
" StopLevel参数点=", DoubleToString(市场当前StopLevel点距, 0),
" FreezeLevel参数点=", DoubleToString(市场当前FreezeLevel点距, 0));
}
// 新建挂单:只按StopLevel把价格修正到合法区域
// 返回修正后的挂单价格
// type仅支持BUY_STOP / SELL_STOP
double 修正新建挂单价格(ENUM_ORDER_TYPE type, double wantedPrice)
{
刷新券商距离限制();
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double minDist = 市场当前StopLevel点距 * vPoint;
double fixedPrice = wantedPrice;
if(type == ORDER_TYPE_BUY_STOP)
{
double minPrice = ask + minDist;
if(fixedPrice < minPrice)
fixedPrice = minPrice;
}
else if(type == ORDER_TYPE_SELL_STOP)
{
double maxPrice = bid - minDist;
if(fixedPrice > maxPrice)
fixedPrice = maxPrice;
}
return NormalizeDouble(fixedPrice, _Digits);
}
// 修改挂单:StopLevel约束目标价;FreezeLevel约束当前挂单是否允许修改。
// 返回false表示当前处于冻结区,本tick跳过修改。
bool 准备修改挂单价格(ENUM_ORDER_TYPE type, double currentOrderPrice, double wantedPrice, double &fixedPrice)
{
刷新券商距离限制();
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double stopDist = 市场当前StopLevel点距 * vPoint;
double freezeDist = 市场当前FreezeLevel点距 * vPoint;
// 订单已进入FreezeLevel范围时,券商通常禁止修改/删除,直接等待下一tick。
if(freezeDist > 0.0)
{
if(type == ORDER_TYPE_BUY_STOP && (currentOrderPrice - ask) <= freezeDist)
return false;
if(type == ORDER_TYPE_SELL_STOP && (bid - currentOrderPrice) <= freezeDist)
return false;
}
fixedPrice = wantedPrice;
if(type == ORDER_TYPE_BUY_STOP)
{
double minPrice = ask + stopDist;
if(fixedPrice < minPrice)
fixedPrice = minPrice;
}
else if(type == ORDER_TYPE_SELL_STOP)
{
double maxPrice = bid - stopDist;
if(fixedPrice > maxPrice)
fixedPrice = maxPrice;
}
fixedPrice = NormalizeDouble(fixedPrice, _Digits);
return true;
}
bool 交易返回成功(uint retcode)
{
return (retcode == TRADE_RETCODE_DONE ||
retcode == TRADE_RETCODE_PLACED ||
retcode == TRADE_RETCODE_DONE_PARTIAL ||
retcode == TRADE_RETCODE_NO_CHANGES);
}
ulong 用户点转原生点(double userPoints)
{
if(userPoints <= 0.0)
return 0;
return (ulong)MathRound(userPoints * vPointFactor);
}
int GetCurrentHour()
{
MqlDateTime dt;
TimeCurrent(dt);
return dt.hour;
}
//+------------------------------------------------------------------+
//| 获取当前时间的分钟数 |
//+------------------------------------------------------------------+
int GetCurrentMinute()
{
MqlDateTime dt;
TimeCurrent(dt);
return dt.min;
}
//+------------------------------------------------------------------+
//| 获取当前时间的秒数 |
//+------------------------------------------------------------------+
int GetCurrentSecond()
{
MqlDateTime dt;
TimeCurrent(dt);
return dt.sec;
}
//+------------------------------------------------------------------+
//| 均价/止盈目标可视化 |
//+------------------------------------------------------------------+
void 删除均价止盈线()
{
ObjectDelete(0,LINE_BUY_AVG);
ObjectDelete(0,LINE_SELL_AVG);
ObjectDelete(0,LINE_BUY_TP);
ObjectDelete(0,LINE_SELL_TP);
ObjectDelete(0,LINE_BASKET_TP);
}
void 设置价格线(string name,double price,color clr,ENUM_LINE_STYLE style,int width,string tip)
{
if(price<=0.0)
{
ObjectDelete(0,name);
return;
}
if(ObjectFind(0,name)<0)
ObjectCreate(0,name,OBJ_HLINE,0,0,price);
ObjectSetDouble(0,name,OBJPROP_PRICE,NormalizeDouble(price,_Digits));
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
ObjectSetInteger(0,name,OBJPROP_STYLE,style);
ObjectSetInteger(0,name,OBJPROP_WIDTH,width);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_SELECTED,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,false);
ObjectSetString(0,name,OBJPROP_TOOLTIP,tip+" "+DoubleToString(price,_Digits));
}
// 估算价格每移动1.0时,该方向持仓浮盈变化金额。
// 使用券商SYMBOL_TRADE_TICK_VALUE_PROFIT / TICK_SIZE,适配黄金/外汇/指数。
double 每价格单位盈亏斜率(double lots)
{
if(lots<=0.0) return 0.0;
double tickSize=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
double tickValue=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT);
if(tickValue<=0.0) tickValue=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
if(tickSize<=0.0 || tickValue<=0.0) return 0.0;
return lots*tickValue/tickSize;
}
void 计算当前止盈价格()
{
g_buyTPPrice=0.0;
g_sellTPPrice=0.0;
g_basketTPPrice=0.0;
g_tpModeText="无持仓";
double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double targetSingle=单边止盈金额*开仓倍数;
double targetBasket=(启用ST顺势逆势恢复模式?ST篮子止盈金额:双边止盈金额)*开仓倍数;
// ST模式始终按全部多空净敞口反算Basket目标价,绝不显示单边TP以免误导拆锁。
if(启用ST顺势逆势恢复模式)
{
if(多单累计手数>0.0 || 空单累计手数>0.0)
{
g_tpModeText="ST Basket止盈";
double buySlope=每价格单位盈亏斜率(多单累计手数);
double sellSlope=每价格单位盈亏斜率(空单累计手数);
double netSlope=buySlope-sellSlope;
double totalProfit=多单累计利润+空单累计利润;
if(MathAbs(netSlope)>0.0000001)
{
double anchor=(netSlope>0.0?bid:ask);
g_basketTPPrice=NormalizeDouble(anchor+(targetBasket-totalProfit)/netSlope,_Digits);
}
else
g_tpModeText="ST Basket·净敞口≈0";
}
return;
}
// 与EA真实平仓逻辑保持同一模式判断。
bool singleMode=(多单累计利润>基准止盈金额 && 空单累计利润>基准止盈金额);
if(singleMode)
{
g_tpModeText="单边止盈";
if(多单累计手数>0.0)
{
double slope=每价格单位盈亏斜率(多单累计手数);
if(slope>0.0)
g_buyTPPrice=NormalizeDouble(bid+(targetSingle-多单累计利润)/slope,_Digits);
}
if(空单累计手数>0.0)
{
double slope=每价格单位盈亏斜率(空单累计手数);
if(slope>0.0)
g_sellTPPrice=NormalizeDouble(ask-(targetSingle-空单累计利润)/slope,_Digits);
}
}
else if(多单累计手数>0.0 || 空单累计手数>0.0)
{
g_tpModeText="双边总止盈";
double buySlope=每价格单位盈亏斜率(多单累计手数);
double sellSlope=每价格单位盈亏斜率(空单累计手数);
double netSlope=buySlope-sellSlope;
double totalProfit=多单累计利润+空单累计利润;
// 多空手数完全对锁时,价格变化几乎无法改变组合利润,因此不存在唯一价格止盈线。
if(MathAbs(netSlope)>0.0000001)
{
double anchor=(netSlope>0.0?bid:ask);
g_basketTPPrice=NormalizeDouble(anchor+(targetBasket-totalProfit)/netSlope,_Digits);
}
else
g_tpModeText="双边止盈·净敞口≈0";
}
}
void 绘制均价止盈线()
{
计算当前止盈价格();
if(!显示均价止盈线)
{
删除均价止盈线();
return;
}
if(多单累计手数>0.0)
设置价格线(LINE_BUY_AVG,多单持仓均价,clrDeepSkyBlue,STYLE_DASH,1,"多单持仓均价");
else ObjectDelete(0,LINE_BUY_AVG);
if(空单累计手数>0.0)
设置价格线(LINE_SELL_AVG,空单持仓均价,clrOrangeRed,STYLE_DASH,1,"空单持仓均价");
else ObjectDelete(0,LINE_SELL_AVG);
if(g_buyTPPrice>0.0)
设置价格线(LINE_BUY_TP,g_buyTPPrice,clrLime,STYLE_SOLID,2,"多单止盈目标");
else ObjectDelete(0,LINE_BUY_TP);
if(g_sellTPPrice>0.0)
设置价格线(LINE_SELL_TP,g_sellTPPrice,clrTomato,STYLE_SOLID,2,"空单止盈目标");
else ObjectDelete(0,LINE_SELL_TP);
if(g_basketTPPrice>0.0)
设置价格线(LINE_BASKET_TP,g_basketTPPrice,clrGold,STYLE_SOLID,2,"双边组合止盈目标");
else ObjectDelete(0,LINE_BASKET_TP);
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
_FirstStep = FirstStep;
if(magicNumber == 0)
{
Alert("请自定义魔术码");
return(INIT_PARAMETERS_INCORRECT);
}
// 2/3位、4/5位报价自动适应
初始化点数自适应();
// 初始化可修改变量
当前_多单_挂单点距_非首单 = 挂单点距_非首单_一阶;
当前_多单_修改挂单起始间距 = 多单_修改挂单起始间距_一阶;
当前_空单_挂单点距_非首单 = 挂单点距_非首单_一阶;
当前_空单_修改挂单起始间距 = 多单_修改挂单起始间距_一阶;
// FirstStep仅受新建挂单StopLevel约束;FreezeLevel不再误用于首挂单距离。
if(_FirstStep < 市场当前StopLevel点距)
{
Print("【挂单距离自动修正】FirstStep=", DoubleToString(_FirstStep,0),
" 小于券商StopLevel=", DoubleToString(市场当前StopLevel点距,0),
",运行时已自动调整。FreezeLevel仅用于修改/删除挂单检查。");
_FirstStep = 市场当前StopLevel点距;
}
Trade.SetExpertMagicNumber(magicNumber);
Trade.SetDeviationInPoints(用户点转原生点(30));
if(启用ST顺势逆势恢复模式)
{
// 本模式需要保留反趋势旧仓,同时允许当前趋势方向建立新仓,因此必须使用Hedging账户。
if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
{
Print("【ST双向管理模式无法启动】当前账户不是Hedging对冲账户;净额账户无法同时保留多空持仓。");
return(INIT_FAILED);
}
g_stAtrHandle=iATR(_Symbol,_Period,Supertrend_ATR周期);
if(g_stAtrHandle==INVALID_HANDLE)
{
Print("【错误】Supertrend ATR句柄创建失败");
return(INIT_FAILED);
}
g_stLastBarTime=0;
UpdateSTDirectionOnClosedBar();
}
// 统计初始化:日期完全采用券商服务器时间 TimeCurrent()
g_sessionStartTime=TimeCurrent();
g_sessionStartBalance=AccountInfoDouble(ACCOUNT_BALANCE);
g_sessionMaxFloatLoss=0.0;
g_statsDay=BrokerDayStart(TimeCurrent());
LoadDailyFloatStats(g_statsDay);
RefreshTodayHistoryStats(true);
设置恒鑫图表主题();
统计持仓信息();
UpdateFloatingStats();
绘制均价止盈线();
Information();
ChartRedraw(0);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
SaveDailyFloatStats(g_statsDay);
DeleteStatsPanelObjects();
ObjectsDeleteAll(0, UI_PREFIX);
ObjectDelete(0, "Close button");
删除均价止盈线();
DeleteSTLines();
if(g_stAtrHandle!=INVALID_HANDLE) { IndicatorRelease(g_stAtrHandle); g_stAtrHandle=INVALID_HANDLE; }
Comment("");
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 成交事件:记录实际成交的逆势恢复加仓 |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(trans.type!=TRADE_TRANSACTION_DEAL_ADD || trans.deal==0) return;
if(!HistoryDealSelect(trans.deal)) return;
if(HistoryDealGetString(trans.deal,DEAL_SYMBOL)!=_Symbol) return;
if((long)HistoryDealGetInteger(trans.deal,DEAL_MAGIC)!=(long)magicNumber) return;
if((ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal,DEAL_ENTRY)!=DEAL_ENTRY_IN) return;
string c=HistoryDealGetString(trans.deal,DEAL_COMMENT);
if(StringFind(c,"MJ_RECOVERY")>=0)
{
RecordRecoveryAddStat();
g_recoveryAddLevel++;
SaveRecoveryState();
Print("【逆势恢复成交】当前逆势加仓层级=",g_recoveryAddLevel,
" 基准手数=",DoubleToString(g_recoveryBaseLots,2));
}
}
void OnTick()
{
int runok = 0;
//int adn[] = {618727, 9317310, 50165780,82002507,20949963, 50166849, 50153572, 1900336531};
datetime expiry_time = D'2999.10.15 00:00';
//检查账号是否在允许列表中
//for(int i = 0; i < ArraySize(adn); i++)
{
//if(AccountInfoInteger(ACCOUNT_LOGIN) == adn)
{
runok = 1;
// break;
}
}
if(runok == 0)
{
Alert("非法账号,请联系v:17630708259");
ExpertRemove();
return;
}
// 检查是否超过有效期
if(TimeCurrent() > expiry_time)
{
Alert("软件已过期,请联系v:17630708259");
ExpertRemove();
return;
}
if(风控管理)
{
亏损达到锁仓();
suo();
}
统计持仓信息();
if(启用ST顺势逆势恢复模式)
{
UpdateSTDirectionOnClosedBar();
UpdateTrendCycleRestartState();
}
RefreshTodayHistoryStats();
UpdateFloatingStats();
绘制均价止盈线();
Information();
// 调整挂单间距 - 使用可修改的变量
if(多单订单数 > 加仓间距变更订单数)
{
当前_多单_挂单点距_非首单 = 挂单点距_非首单_二阶;
当前_多单_修改挂单起始间距 = 修改挂单起始间距_二阶;
}
else
{
当前_多单_挂单点距_非首单 = 挂单点距_非首单_一阶;
当前_多单_修改挂单起始间距 = 多单_修改挂单起始间距_一阶;
}
if(空单订单数 > 加仓间距变更订单数)
{
当前_空单_挂单点距_非首单 = 挂单点距_非首单_二阶;
当前_空单_修改挂单起始间距 = 修改挂单起始间距_二阶;
}
else
{
当前_空单_挂单点距_非首单 = 挂单点距_非首单_一阶;
当前_空单_修改挂单起始间距 = 多单_修改挂单起始间距_一阶;
}
// 止盈止损逻辑
if(启用ST顺势逆势恢复模式)
{
// 非对称退出规则:
// 1) 顺势+逆势整体达到Basket目标 -> 全部退出;
// 2) 逆势恢复组自己转为盈利 -> 允许逆势方向独立退出;
// 3) 只要逆势组还存在,顺势组没有独立盈利退出权;
// 4) 逆势组已退出后,剩余顺势仓达到同一ST止盈目标即可正常退出。
double basketProfit=多单累计利润+空单累计利润;
double basketTarget=ST篮子止盈金额*开仓倍数;
int inverseDir=(g_stDir==0?0:-g_stDir);
double inverseLots=(inverseDir==1?多单累计手数 inverseDir==-1?空单累计手数:0.0));
double inverseProfit=(inverseDir==1?多单累计利润 inverseDir==-1?空单累计利润:0.0));
if(basketProfit>=basketTarget)
{
Print("【ST Basket止盈】总净利润=",DoubleToString(basketProfit,2));
平仓及删除订单(0);
ClearRecoveryState();
return;
}
double inverseExitTarget=逆势独立退出净利润*开仓倍数;
bool inverseProfitable=(inverseLots>0.0 &&
((inverseExitTarget<=0.0 && inverseProfit>0.0) ||
(inverseExitTarget>0.0 && inverseProfit>=inverseExitTarget)));
if(逆势盈利允许独立退出 && inverseProfitable)
{
Print("【逆势独立盈利退出】方向=",(inverseDir==1?"BUY":"SELL"),
" 净利润=",DoubleToString(inverseProfit,2),
";顺势仓保留继续运行");
DeleteRecoveryPending(inverseDir);
平仓及删除订单(inverseDir);
ClearRecoveryState();
return;
}
if(basketProfit<=-StopLoss*开仓倍数)
{
Print("【ST Basket止损】",DoubleToString(basketProfit,2));
平仓及删除订单(0);
ClearRecoveryState();
return;
}
}
else
{
if(多单累计利润 > 基准止盈金额 && 空单累计利润 > 基准止盈金额)
{
if(多单累计利润 >= 单边止盈金额 * 开仓倍数)
{
Print("Buy Profit ", 多单累计利润);
平仓及删除订单(1);
return;
}
if(空单累计利润 >= 单边止盈金额 * 开仓倍数)
{
Print("Sell Profit ", 空单累计利润);
平仓及删除订单(-1);
return;
}
}
else
{
double 多空总利润 = 多单累计利润 + 空单累计利润;
if(多空总利润 >= 双边止盈金额 * 开仓倍数)
{
Print("CloseAll ", 多空总利润);
平仓及删除订单(0);
return;
}
}
if(多单累计利润 <= -StopLoss * 开仓倍数)
{
Print("Buy Loss ", 多单累计利润);
平仓及删除订单(1);
return;
}
if(空单累计利润 <= -StopLoss * 开仓倍数)
{
Print("Sell Loss ", 空单累计利润);
平仓及删除订单(-1);
return;
}
}
// 获取当前时间
int currentHour = GetCurrentHour();
if(!启用ST顺势逆势恢复模式)
{
// 开单逻辑
//Print(多单_挂单订单数 == 0 ,"||", 多单累计利润 > by_do_17 ,"||", allow_buy); ExpertRemove();
if(多单_挂单订单数 == 0 && 多单累计利润 > by_do_17 && allow_buy)
{
double trade_price, trade_lot;
bool isFirst = (多单订单数 == 0);
//问题在这里
if(isFirst)
trade_price = NormalizeDouble(_FirstStep * vPoint + SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
else
trade_price = NormalizeDouble(当前_多单_挂单点距_非首单 * vPoint + SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
if(多单订单数 == 0 || (多单最高价 != 0 && trade_price >= NormalizeDouble(当前_多单_修改挂单起始间距 * vPoint + 多单最高价, _Digits) && by_bo_10) ||
(多单最低价 != 0 && trade_price <= NormalizeDouble(多单最低价 - 当前_多单_修改挂单起始间距 * vPoint, _Digits)))
{
if(多单订单数 == 0)
trade_lot = 原始初始手数 * 开仓倍数;
else
trade_lot = NormalizeDouble(多单订单数 * by_do_19 + 原始初始手数 * MathPow(逆势加仓手数递增比例, 多单订单数), 开仓手数小数位) * 开仓倍数;
if(((trade_lot < AccountInfoDouble(ACCOUNT_FREEMARGIN) / SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE) && 多单订单数 > 0) || by_bo_9))
{
if((sj == false || (sj == true && currentHour > star && currentHour < end)) && runEA == true)
{
if(!isFirst || (isFirst && !isInDangerTime(dangerHours)))
{
double wantedPrice = trade_price;
trade_price = 修正新建挂单价格(ORDER_TYPE_BUY_STOP, trade_price);
if(MathAbs(trade_price - wantedPrice) >= vRawPoint * 0.5)
Print("【BuyStop合法性修正】目标价=", DoubleToString(wantedPrice,_Digits),
" 修正价=", DoubleToString(trade_price,_Digits),
" StopLevel参数点=", DoubleToString(市场当前StopLevel点距,0));
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_PENDING;
request.symbol = _Symbol;
request.volume = trade_lot;
request.price = trade_price;
request.sl = 0;
request.tp = 0;
request.type = ORDER_TYPE_BUY_STOP;
request.magic = magicNumber;
request.comment = comm + ":" + magicNumber;
ResetLastError();
bool sent = OrderSend(request, result);
if(sent && 交易返回成功(result.retcode))
Print("【BuyStop成功】ticket=", result.order, " retcode=", result.retcode, " ", result.comment);
else
Print("【BuyStop失败】sent=", sent, " retcode=", result.retcode,
" comment=", result.comment, " lastError=", GetLastError(),
" price=", DoubleToString(trade_price,_Digits));
}
}
}
}
}
// 空单开单逻辑
if(空单_挂单订单数 == 0 && 空单累计利润 > by_do_17 && allow_sell)
{
double trade_price, trade_lot;
bool isFirst = (空单订单数 == 0);
if(isFirst)
trade_price = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - _FirstStep * vPoint, _Digits);
else
trade_price = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - 当前_空单_挂单点距_非首单 * vPoint, _Digits);
if(空单订单数 == 0 || (空单最低价 != 0 && trade_price <= NormalizeDouble(空单最低价 - 当前_空单_修改挂单起始间距 * vPoint, _Digits) && by_bo_10) ||
(空单最高价 != 0 && trade_price >= NormalizeDouble(当前_空单_修改挂单起始间距 * vPoint + 空单最高价, _Digits)))
{
if(空单订单数 == 0)
trade_lot = 原始初始手数 * 开仓倍数;
else
trade_lot = NormalizeDouble(空单订单数 * by_do_19 + 原始初始手数 * MathPow(逆势加仓手数递增比例, 空单订单数), 开仓手数小数位) * 开仓倍数;
if(((trade_lot < AccountInfoDouble(ACCOUNT_FREEMARGIN) / SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE) && 空单订单数 > 0) || by_bo_9))
{
if((sj == false || (sj == true && currentHour > star && currentHour < end)) && runEA == true)
{
if(!isFirst || (isFirst && !isInDangerTime(dangerHours)))
{
double wantedPrice = trade_price;
trade_price = 修正新建挂单价格(ORDER_TYPE_SELL_STOP, trade_price);
if(MathAbs(trade_price - wantedPrice) >= vRawPoint * 0.5)
Print("【SellStop合法性修正】目标价=", DoubleToString(wantedPrice,_Digits),
" 修正价=", DoubleToString(trade_price,_Digits),
" StopLevel参数点=", DoubleToString(市场当前StopLevel点距,0));
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_PENDING;
request.symbol = _Symbol;
request.volume = trade_lot;
request.price = trade_price;
request.sl = 0;
request.tp = 0;
request.type = ORDER_TYPE_SELL_STOP;
request.magic = magicNumber;
request.comment = comm + ":" + magicNumber;
ResetLastError();
bool sent = OrderSend(request, result);
if(sent && 交易返回成功(result.retcode))
Print("【SellStop成功】ticket=", result.order, " retcode=", result.retcode, " ", result.comment);
else
Print("【SellStop失败】sent=", sent, " retcode=", result.retcode,
" comment=", result.comment, " lastError=", GetLastError(),
" price=", DoubleToString(trade_price,_Digits));
}
}
}
}
}
// 移动挂单逻辑
移动挂单订单();
}
else
{
// ST决定“新周期”方向:趋势方向可开首仓并顺势回调加仓;
// 反趋势方向禁止从0开首仓,但已有旧仓允许按V1.07原始逆势规则继续恢复加仓。
ProcessTrendPullbackAdd();
ProcessInverseRecoveryAdd();
}
}
//+------------------------------------------------------------------+
//| 统计持仓信息 |
//+------------------------------------------------------------------+
void 统计持仓信息()
{
多单累计利润 = 0; 空单累计利润 = 0;
多单累计手数 = 0; 空单累计手数 = 0;
多单订单数 = 0; 空单订单数 = 0;
多单_挂单订单数 = 0; 空单_挂单订单数 = 0;
多单最高价 = 0; 多单最低价 = 0;
空单最高价 = 0; 空单最低价 = 0;
多单持仓均价 = 0; 空单持仓均价 = 0;
double 多单面值累计 = 0, 空单面值累计 = 0;
// 统计持仓
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionInfo.SelectByIndex(i))
{
if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == magicNumber)
{
double profit = PositionInfo.Profit() + PositionInfo.Swap() + PositionInfo.Commission();
double volume = PositionInfo.Volume();
double openPrice = PositionInfo.PriceOpen();
if(PositionInfo.PositionType() == POSITION_TYPE_BUY)
{
多单订单数++;
多单累计手数 += volume;
多单面值累计 += openPrice * volume;
多单累计利润 += profit;
if(多单最高价 < openPrice || 多单最高价 == 0)
多单最高价 = openPrice;
if(多单最低价 > openPrice || 多单最低价 == 0)
多单最低价 = openPrice;
}
else if(PositionInfo.PositionType() == POSITION_TYPE_SELL)
{
空单订单数++;
空单累计手数 += volume;
空单面值累计 += openPrice * volume;
空单累计利润 += profit;
if(空单最低价 > openPrice || 空单最低价 == 0)
空单最低价 = openPrice;
if(空单最高价 < openPrice || 空单最高价 == 0)
空单最高价 = openPrice;
}
}
}
}
// 统计挂单
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderInfo.SelectByIndex(i))
{
if(OrderInfo.Symbol() == _Symbol && OrderInfo.Magic() == magicNumber)
{
double openPrice = OrderInfo.PriceOpen();
if(OrderInfo.OrderType() == ORDER_TYPE_BUY_STOP)
{
多单_挂单订单数++;
if(多单最高价 < openPrice || 多单最高价 == 0)
多单最高价 = openPrice;
}
else if(OrderInfo.OrderType() == ORDER_TYPE_SELL_STOP)
{
空单_挂单订单数++;
if(空单最低价 > openPrice || 空单最低价 == 0)
空单最低价 = openPrice;
}
}
}
}
// 计算持仓均价
if(多单累计手数 > 0)
多单持仓均价 = NormalizeDouble(多单面值累计 / 多单累计手数, _Digits);
if(空单累计手数 > 0)
空单持仓均价 = NormalizeDouble(空单面值累计 / 空单累计手数, _Digits);
}
//+------------------------------------------------------------------+
//| 平仓及删除订单 |
//+------------------------------------------------------------------+
int 平仓及删除订单(int direction)
{
int closed = 0;
// 平仓持仓
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionInfo.SelectByIndex(i))
{
if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == magicNumber)
{
bool shouldClose = false;
if((direction == 1 || direction == 0) && PositionInfo.PositionType() == POSITION_TYPE_BUY)
shouldClose = true;
else if((direction == -1 || direction == 0) && PositionInfo.PositionType() == POSITION_TYPE_SELL)
shouldClose = true;
if(shouldClose)
{
if(Trade.PositionClose(PositionInfo.Ticket()))
closed++;
else
Print("Close position failed: ", GetLastError());
}
}
}
}
// 删除挂单
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderInfo.SelectByIndex(i))
{
if(OrderInfo.Symbol() == _Symbol && OrderInfo.Magic() == magicNumber)
{
bool shouldDelete = false;
if((direction == 1 || direction == 0) && OrderInfo.OrderType() == ORDER_TYPE_BUY_STOP)
shouldDelete = true;
else if((direction == -1 || direction == 0) && OrderInfo.OrderType() == ORDER_TYPE_SELL_STOP)
shouldDelete = true;
if(shouldDelete)
{
if(Trade.OrderDelete(OrderInfo.Ticket()))
closed++;
else
Print("Delete order failed: ", GetLastError());
}
}
}
}
return closed;
}
//+------------------------------------------------------------------+
//| 是否当前时间是危险时间段 |
//+------------------------------------------------------------------+
bool isInDangerTime(string skipHours)
{
int hour = GetCurrentHour();
string sep = ",";
ushort u_sep = StringGetCharacter(sep, 0);
string skipHoursArr[];
int k = StringSplit(skipHours, u_sep, skipHoursArr);
if(k > 0)
{
for(int i = 0; i < ArraySize(skipHoursArr); i++)
{
if(hour == (int)StringToInteger(skipHoursArr))
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| 移动挂单订单逻辑 |
//+------------------------------------------------------------------+
void 移动挂单订单()
{
// 遍历所有挂单
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderInfo.SelectByIndex(i))
{
if(OrderInfo.Symbol() == _Symbol && OrderInfo.Magic() == magicNumber)
{
ulong orderTicket = OrderInfo.Ticket();
double currentPrice = OrderInfo.PriceOpen();
double newPrice = 0;
bool shouldModify = false;
if(OrderInfo.OrderType() == ORDER_TYPE_BUY_STOP && allow_buy)
{
if(多单订单数 == 0)
newPrice = NormalizeDouble(_FirstStep * vPoint + SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
else
newPrice = NormalizeDouble(当前_多单_挂单点距_非首单 * vPoint + SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
if(NormalizeDouble(currentPrice - stop挂单改价间距 * vPoint, _Digits) > newPrice &&
(newPrice <= NormalizeDouble(多单最低价 - 当前_多单_修改挂单起始间距 * vPoint, _Digits) || 多单最低价 == 0 || by_bo_10) &&
(多单订单数 == 0 || newPrice >= NormalizeDouble(当前_多单_修改挂单起始间距 * vPoint + 多单最高价, _Digits) ||
newPrice <= NormalizeDouble(多单最低价 - 当前_多单_修改挂单起始间距 * vPoint, _Digits)))
{
shouldModify = true;
}
}
else if(OrderInfo.OrderType() == ORDER_TYPE_SELL_STOP && allow_sell)
{
if(空单订单数 == 0)
newPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - _FirstStep * vPoint, _Digits);
else
newPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - 当前_空单_挂单点距_非首单 * vPoint, _Digits);
if(NormalizeDouble(currentPrice + stop挂单改价间距 * vPoint, _Digits) < newPrice &&
(newPrice >= NormalizeDouble(当前_空单_修改挂单起始间距 * vPoint + 空单最高价, _Digits) || 空单最高价 == 0 || by_bo_10) &&
(空单订单数 == 0 || newPrice <= NormalizeDouble(空单最低价 - 当前_空单_修改挂单起始间距 * vPoint, _Digits) ||
newPrice >= NormalizeDouble(当前_空单_修改挂单起始间距 * vPoint + 空单最高价, _Digits)))
{
shouldModify = true;
}
}
if(shouldModify && newPrice > 0)
{
ENUM_ORDER_TYPE ordType = OrderInfo.OrderType();
double wantedPrice = newPrice;
double legalPrice = newPrice;
if(!准备修改挂单价格(ordType, currentPrice, wantedPrice, legalPrice))
{
Print("【挂单修改跳过】ticket=", orderTicket,
" 已进入FreezeLevel范围 Freeze参数点=", DoubleToString(市场当前FreezeLevel点距,0));
continue;
}
newPrice = legalPrice;
if(MathAbs(newPrice - wantedPrice) >= vRawPoint * 0.5)
Print("【挂单修改合法性修正】ticket=", orderTicket,
" 目标价=", DoubleToString(wantedPrice,_Digits),
" 修正价=", DoubleToString(newPrice,_Digits),
" StopLevel参数点=", DoubleToString(市场当前StopLevel点距,0));
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_MODIFY;
request.order = orderTicket;
request.price = newPrice;
request.sl = OrderInfo.StopLoss();
request.tp = OrderInfo.TakeProfit();
ResetLastError();
bool sent = OrderSend(request, result);
if(sent && 交易返回成功(result.retcode))
Print("【挂单修改成功】ticket=", orderTicket, " newPrice=", DoubleToString(newPrice,_Digits),
" retcode=", result.retcode, " ", result.comment);
else
Print("【挂单修改失败】ticket=", orderTicket, " sent=", sent,
" retcode=", result.retcode, " comment=", result.comment,
" lastError=", GetLastError(), " price=", DoubleToString(newPrice,_Digits));
}
}
}
}
}
//+------------------------------------------------------------------+
//| 恒鑫风格图表主题 |
//+------------------------------------------------------------------+
void 设置恒鑫图表主题()
{
if(!启用恒鑫图表配色) return;
ChartSetInteger(0, CHART_COLOR_BACKGROUND, C'6,6,8');
ChartSetInteger(0, CHART_COLOR_FOREGROUND, C'212,175,55');
ChartSetInteger(0, CHART_COLOR_GRID, C'26,22,12');
ChartSetInteger(0, CHART_COLOR_CHART_LINE, C'212,175,55');
ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, C'46,204,113');
ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, C'231,76,60');
ChartSetInteger(0, CHART_COLOR_CHART_UP, C'46,204,113');
ChartSetInteger(0, CHART_COLOR_CHART_DOWN, C'231,76,60');
ChartSetInteger(0, CHART_COLOR_VOLUME, C'160,128,48');
ChartSetInteger(0, CHART_COLOR_BID, C'231,76,60');
ChartSetInteger(0, CHART_COLOR_ASK, C'46,204,113');
ChartSetInteger(0, CHART_FOREGROUND, false);
}
void UI_Rect(string name,int x,int y,int w,int h,color bg,color border)
{
if(ObjectFind(0,name)<0) ObjectCreate(0,name,OBJ_RECTANGLE_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bg);
ObjectSetInteger(0,name,OBJPROP_COLOR,border);
ObjectSetInteger(0,name,OBJPROP_BORDER_TYPE,BORDER_FLAT);
ObjectSetInteger(0,name,OBJPROP_BACK,false);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
ObjectSetInteger(0,name,OBJPROP_ZORDER,5);
}
void UI_Label(string name,int x,int y,string text,color clr,int size=10,string font="Microsoft YaHei")
{
if(ObjectFind(0,name)<0)
{
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,size);
ObjectSetString(0,name,OBJPROP_FONT,font);
ObjectSetInteger(0,name,OBJPROP_BACK,false);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
ObjectSetInteger(0,name,OBJPROP_ZORDER,20);
}
ObjectSetString(0,name,OBJPROP_TEXT,text);
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
}
void UI_Value(string name,int rightX,int y,string text,color clr,int size=10,string font="Consolas")
{
if(ObjectFind(0,name)<0)
{
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,rightX);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,size);
ObjectSetString(0,name,OBJPROP_FONT,font);
ObjectSetInteger(0,name,OBJPROP_BACK,false);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
ObjectSetInteger(0,name,OBJPROP_ZORDER,20);
}
ObjectSetString(0,name,OBJPROP_TEXT,text);
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
}
void UI_Button(string name,string text,int x,int y,int w,int h,color bg,color fg)
{
if(ObjectFind(0,name)<0) ObjectCreate(0,name,OBJ_BUTTON,0,0,0);
ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bg);
ObjectSetInteger(0,name,OBJPROP_COLOR,fg);
ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,C'90,100,115');
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,11);
ObjectSetString(0,name,OBJPROP_FONT,"Microsoft YaHei");
ObjectSetString(0,name,OBJPROP_TEXT,text);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
ObjectSetInteger(0,name,OBJPROP_ZORDER,30);
}
//+------------------------------------------------------------------+
//| ST界面仓位角色统计(只读,不参与交易计算) |
//+------------------------------------------------------------------+
void GetPositionRoleStats(double &buyLockLots,double &sellLockLots,
double &buyTrendLots,double &sellTrendLots,
double &buyOtherLots,double &sellOtherLots,
double &buyLockProfit,double &sellLockProfit,
double &buyTrendProfit,double &sellTrendProfit,
double &buyOtherProfit,double &sellOtherProfit)
{
buyLockLots=sellLockLots=buyTrendLots=sellTrendLots=buyOtherLots=sellOtherLots=0.0;
buyLockProfit=sellLockProfit=buyTrendProfit=sellTrendProfit=buyOtherProfit=sellOtherProfit=0.0;
for(int i=0;i< ositionsTotal();i++)
{
if(!PositionInfo.SelectByIndex(i)) continue;
if(PositionInfo.Symbol()!=_Symbol || PositionInfo.Magic()!=magicNumber) continue;
double vol=PositionInfo.Volume();
double pnl=PositionInfo.Profit()+PositionInfo.Swap()+PositionInfo.Commission();
string c=PositionInfo.Comment();
bool isLock=(StringFind(c,"MJ_RECOVERY")>=0);
bool isTrend=(StringFind(c,"MJ_TREND")>=0);
if(PositionInfo.PositionType()==POSITION_TYPE_BUY)
{
if(isLock){ buyLockLots+=vol; buyLockProfit+=pnl; }
else if(isTrend){ buyTrendLots+=vol; buyTrendProfit+=pnl; }
else { buyOtherLots+=vol; buyOtherProfit+=pnl; }
}
else if(PositionInfo.PositionType()==POSITION_TYPE_SELL)
{
if(isLock){ sellLockLots+=vol; sellLockProfit+=pnl; }
else if(isTrend){ sellTrendLots+=vol; sellTrendProfit+=pnl; }
else { sellOtherLots+=vol; sellOtherProfit+=pnl; }
}
}
}
//+------------------------------------------------------------------+
//| 展开式每日统计:券商服务器日期,最近7天 |
//+------------------------------------------------------------------+
void DrawDailyStatsPanel(int x0,int y0,int mainW)
{
if(!g_statsPanelOpen)
{
DeleteStatsPanelObjects();
return;
}
const color cBG=C'13,17,23', cCard=C'20,26,34', cText=C'225,231,239', cSub=C'143,155,171';
const color cGreen=C'46,204,113', cRed=C'231,76,60', cBlue=C'52,164,235', cOrange=C'240,173,78';
string uiFont="Microsoft YaHei", mono="Consolas";
int W=1290,H=350,x=x0+mainW+12,y=y0;
UI_Rect(STAT_UI_PREFIX+"BG",x,y,W,H,cBG,C'76,86,101');
UI_Rect(STAT_UI_PREFIX+"Top",x+1,y+1,W-2,3,cBlue,cBlue);
UI_Label(STAT_UI_PREFIX+"Title",x+16,y+14,"统计 · 最近7天(券商时间)",cText,15,uiFont);
UI_Label(STAT_UI_PREFIX+"Note",x+16,y+44,"成交类数据从历史重建;浮盈/浮亏及ST切换、逆势加仓次数按券商自然日记录",cSub,9,uiFont);
string heads[12]={"日期","已实现","开仓手数","最大单笔","平仓","盈利/亏损","胜率","最大浮盈","最大浮亏","ST切换","逆势加仓","净值"};
int widths[12]={76,100,96,92,72,105,72,100,100,82,72,108};
int colX[12]; colX[0]=x+14;
for(int i=1;i<12;i++) colX=colX[i-1]+widths[i-1];
int hy=y+84;
UI_Rect(STAT_UI_PREFIX+"Header",x+10,hy-7,W-20,28,C'24,30,39',C'24,30,39');
for(int c=0;c<12;c++) UI_Label(STAT_UI_PREFIX+"H"+IntegerToString(c),colX[c],hy,heads[c],cSub,9,uiFont);
datetime today=BrokerDayStart(TimeCurrent());
for(int r=0;r<7;r++)
{
datetime d=today-r*86400;
int yy=hy+34+r*31;
if(r%2==1) UI_Rect(STAT_UI_PREFIX+"RowBG"+IntegerToString(r),x+10,yy-6,W-20,27,cCard,cCard);
double pnl=0,lots=0,maxLot=0; int trades=0,wins=0,losses=0;
CalcDailyHistoryStats(d,pnl,lots,maxLot,trades,wins,losses);
string k=DailyStatPrefix(d);
double maxF=GlobalVariableCheck(k+"maxFloat")?GlobalVariableGet(k+"maxFloat"):0.0;
double minF=GlobalVariableCheck(k+"minFloat")?GlobalVariableGet(k+"minFloat"):0.0;
double eq=GlobalVariableCheck(k+"lastEq")?GlobalVariableGet(k+"lastEq"):0.0;
int stFlips=GlobalVariableCheck(k+"stFlips")?(int)GlobalVariableGet(k+"stFlips"):0;
int recoveryAdds=GlobalVariableCheck(k+"recoveryCount")?(int)GlobalVariableGet(k+"recoveryCount"):0;
if(d==g_statsDay)
{
pnl=g_todayRealizedProfit; lots=g_todayOpenedLots; maxLot=g_todayMaxEntryLot;
trades=g_todayCloseTrades; wins=g_todayWins; losses=g_todayLosses;
maxF=g_todayMaxFloatProfit; minF=g_todayMaxFloatLoss;
stFlips=g_todaySTFlips; recoveryAdds=g_todayRecoveryAdds;
eq=AccountInfoDouble(ACCOUNT_EQUITY);
}
int decided=wins+losses;
double wr=(decided>0?100.0*wins/decided:0.0);
MqlDateTime dd; TimeToStruct(d,dd);
string vals[12];
vals[0]=StringFormat("%02d.%02d",dd.mon,dd.day);
vals[1]=DoubleToString(pnl,2);
vals[2]=DoubleToString(lots,2);
vals[3]=(maxLot>0?DoubleToString(maxLot,2):"-");
vals[4]=IntegerToString(trades);
vals[5]=IntegerToString(wins)+" / "+IntegerToString(losses);
vals[6]=DoubleToString(wr,1)+"%";
vals[7]=DoubleToString(maxF,2);
vals[8]=DoubleToString(minF,2);
vals[9]=IntegerToString(stFlips);
vals[10]=IntegerToString(recoveryAdds);
vals[11]=(eq>0?DoubleToString(eq,2):"-");
for(int c=0;c<12;c++)
{
color cc=cText;
if(c==1) cc=(pnl>=0?cGreen:cRed);
else if(c==7) cc=cGreen;
else if(c==8) cc=cRed;
else if(c==9) cc=cBlue;
else if(c==10) cc=cOrange;
UI_Label(STAT_UI_PREFIX+"R"+IntegerToString(r)+"C"+IntegerToString(c),colX[c],yy,vals[c],cc,9,(c==0?uiFont:mono));
}
}
}
//+------------------------------------------------------------------+
//| 显示界面信息:恒鑫风格卡片面板 |
//+------------------------------------------------------------------+
void Information()
{
if(!启用恒鑫风格界面)
{
DeleteStatsPanelObjects();
ObjectsDeleteAll(0, UI_PREFIX);
ObjectDelete(0,"Close button");
Comment("");
return;
}
const color cBG=C'13,17,23', cCard=C'20,26,34', cCard2=C'17,22,29';
const color cBorder=C'58,67,80', cText=C'225,231,239', cSub=C'143,155,171';
const color cBlue=C'52,164,235', cGreen=C'46,204,113', cRed=C'231,76,60', cOrange=C'240,173,78';
string uiFont="Microsoft YaHei", mono="Consolas";
int x0=界面X, y0=界面Y, W=MathMax(界面宽度,780), H=(启用ST顺势逆势恢复模式?684:520);
int pad=12, gap=8, innerW=W-pad*2;
int colW=(innerW-gap*2)/3;
int x1=x0+pad, x2=x1+colW+gap, x3=x2+colW+gap;
int ldx=14, line=22;
UI_Rect(UI_BG,x0,y0,W,H,cBG,C'76,86,101');
UI_Rect(UI_PREFIX+"TopLine",x0+1,y0+1,W-2,3,cBlue,cBlue);
double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
double spreadPts=(vPoint>0 ? (ask-bid)/vPoint : 0.0);
double totalProfit=多单累计利润+空单累计利润;
double totalLots=多单累计手数+空单累计手数;
int totalPos=多单订单数+空单订单数;
int totalPending=多单_挂单订单数+空单_挂单订单数;
double balance=AccountInfoDouble(ACCOUNT_BALANCE);
double equity=AccountInfoDouble(ACCOUNT_EQUITY);
double buyLockLots,sellLockLots,buyTrendLots,sellTrendLots,buyOtherLots,sellOtherLots;
double buyLockProfit,sellLockProfit,buyTrendProfit,sellTrendProfit,buyOtherProfit,sellOtherProfit;
GetPositionRoleStats(buyLockLots,sellLockLots,buyTrendLots,sellTrendLots,buyOtherLots,sellOtherLots,
buyLockProfit,sellLockProfit,buyTrendProfit,sellTrendProfit,buyOtherProfit,sellOtherProfit);
int headerY=y0+13;
UI_Label(UI_PREFIX+"Title",x1,headerY,"摸金校尉 · MT5 V1.14",cText,16,uiFont);
UI_Label(UI_PREFIX+"Symbol",x0+270,headerY+2,_Symbol,cOrange,12,mono);
UI_Label(UI_PREFIX+"Spread",x0+430,headerY+3,"Spread "+DoubleToString(spreadPts,1),cSub,10,mono);
UI_Value(UI_PREFIX+"State",x0+W-170,headerY+2,(runEA?"● 运行中":"● 暂停"),(runEA?cGreen:cOrange),10,uiFont);
UI_Button(UI_PREFIX+"StatsBtn","统计",x0+W-76,y0+9,62,30,C'48,56,68',clrWhite);
// ===== 第一排:BUY / SELL / BASKET =====
int row1Y=y0+52, row1H=142, vy=row1Y+43;
UI_Rect(UI_PREFIX+"BuyCard",x1,row1Y,colW,row1H,cCard,cBorder);
UI_Rect(UI_PREFIX+"SellCard",x2,row1Y,colW,row1H,cCard,cBorder);
UI_Rect(UI_PREFIX+"TotalCard",x3,row1Y,colW,row1H,cCard2,cBorder);
UI_Label(UI_PREFIX+"BuyH",x1+ldx,row1Y+12,"多单 BUY",cGreen,13,uiFont);
UI_Label(UI_PREFIX+"BuyL1",x1+ldx,vy,"持仓 / 手数",cSub,10,uiFont); UI_Value(UI_PREFIX+"BuyV1",x1+colW-14,vy,IntegerToString(多单订单数)+" / "+DoubleToString(多单累计手数,2),cText,10,mono);
UI_Label(UI_PREFIX+"BuyL2",x1+ldx,vy+line,"趋势仓",cSub,10,uiFont); UI_Value(UI_PREFIX+"BuyV2",x1+colW-14,vy+line,DoubleToString(buyTrendLots,2)+" Lot",cGreen,10,mono);
UI_Label(UI_PREFIX+"BuyL3",x1+ldx,vy+line*2,"逆势加仓",cSub,10,uiFont); UI_Value(UI_PREFIX+"BuyV3",x1+colW-14,vy+line*2,DoubleToString(buyLockLots,2)+" Lot",cOrange,10,mono);
UI_Label(UI_PREFIX+"BuyL4",x1+ldx,vy+line*3,"浮盈",cSub,10,uiFont); UI_Value(UI_PREFIX+"BuyV4",x1+colW-14,vy+line*3,"$"+DoubleToString(多单累计利润,2),(多单累计利润>=0?cGreen:cRed),11,mono);
UI_Label(UI_PREFIX+"SellH",x2+ldx,row1Y+12,"空单 SELL",cRed,13,uiFont);
UI_Label(UI_PREFIX+"SellL1",x2+ldx,vy,"持仓 / 手数",cSub,10,uiFont); UI_Value(UI_PREFIX+"SellV1",x2+colW-14,vy,IntegerToString(空单订单数)+" / "+DoubleToString(空单累计手数,2),cText,10,mono);
UI_Label(UI_PREFIX+"SellL2",x2+ldx,vy+line,"趋势仓",cSub,10,uiFont); UI_Value(UI_PREFIX+"SellV2",x2+colW-14,vy+line,DoubleToString(sellTrendLots,2)+" Lot",cRed,10,mono);
UI_Label(UI_PREFIX+"SellL3",x2+ldx,vy+line*2,"逆势加仓",cSub,10,uiFont); UI_Value(UI_PREFIX+"SellV3",x2+colW-14,vy+line*2,DoubleToString(sellLockLots,2)+" Lot",cOrange,10,mono);
UI_Label(UI_PREFIX+"SellL4",x2+ldx,vy+line*3,"浮盈",cSub,10,uiFont); UI_Value(UI_PREFIX+"SellV4",x2+colW-14,vy+line*3,"$"+DoubleToString(空单累计利润,2),(空单累计利润>=0?cGreen:cRed),11,mono);
double netLots=多单累计手数-空单累计手数;
string netText=(MathAbs(netLots)<0.000001?"0.00" netLots>0?"BUY ":"SELL ")+DoubleToString(MathAbs(netLots),2));
double basketTarget=(启用ST顺势逆势恢复模式?ST篮子止盈金额:双边止盈金额)*开仓倍数;
double remain=basketTarget-totalProfit;
UI_Label(UI_PREFIX+"TotalH",x3+ldx,row1Y+12,"BASKET 一篮子",cText,13,uiFont);
UI_Label(UI_PREFIX+"TotalL1",x3+ldx,vy,"仓位 / 挂单",cSub,10,uiFont); UI_Value(UI_PREFIX+"TotalV1",x3+colW-14,vy,IntegerToString(totalPos)+" / "+IntegerToString(totalPending),cText,10,mono);
UI_Label(UI_PREFIX+"TotalL2",x3+ldx,vy+line,"当前盈亏",cSub,10,uiFont); UI_Value(UI_PREFIX+"TotalV2",x3+colW-14,vy+line,"$"+DoubleToString(totalProfit,2),(totalProfit>=0?cGreen:cRed),11,mono);
UI_Label(UI_PREFIX+"TotalL3",x3+ldx,vy+line*2,"净敞口",cSub,10,uiFont); UI_Value(UI_PREFIX+"TotalV3",x3+colW-14,vy+line,netText,(netLots>=0?cGreen:cRed),10,mono);
UI_Label(UI_PREFIX+"TotalL4",x3+ldx,vy+line*3,"距止盈",cSub,10,uiFont); UI_Value(UI_PREFIX+"TotalV4",x3+colW-14,vy+line*3,"$"+DoubleToString(remain,2),(remain<=0?cGreen:cOrange),10,mono);
// ===== 第二排:账户 / 趋势 / 锁仓 =====
int row2Y=row1Y+row1H+gap, row2H=(启用ST顺势逆势恢复模式?164:126), rvy=row2Y+43;
UI_Rect(UI_PREFIX+"AccCard",x1,row2Y,colW,row2H,cCard,cBorder);
UI_Rect(UI_PREFIX+"TrendCard",x2,row2Y,colW,row2H,cCard,cBorder);
UI_Rect(UI_PREFIX+"LockCard",x3,row2Y,colW,row2H,cCard,cBorder);
UI_Label(UI_PREFIX+"AccH",x1+ldx,row2Y+12,"账户 / 今日",cText,13,uiFont);
UI_Label(UI_PREFIX+"AccL1",x1+ldx,rvy,"余额",cSub,10,uiFont); UI_Value(UI_PREFIX+"AccV1",x1+colW-14,rvy,"$"+DoubleToString(balance,2),cText,10,mono);
UI_Label(UI_PREFIX+"AccL2",x1+ldx,rvy+line,"净值",cSub,10,uiFont); UI_Value(UI_PREFIX+"AccV2",x1+colW-14,rvy+line,"$"+DoubleToString(equity,2),cText,10,mono);
UI_Label(UI_PREFIX+"AccL3",x1+ldx,rvy+line*2,"今日利润",cSub,10,uiFont); UI_Value(UI_PREFIX+"AccV3",x1+colW-14,rvy+line*2,"$"+DoubleToString(g_todayRealizedProfit,2),(g_todayRealizedProfit>=0?cGreen:cRed),10,mono);
UI_Label(UI_PREFIX+"AccL4",x1+ldx,rvy+line*3,"当前浮亏",cSub,10,uiFont); UI_Value(UI_PREFIX+"AccV4",x1+colW-14,rvy+line*3,"$"+DoubleToString((totalProfit<0?totalProfit:0.0),2),(totalProfit<0?cRed:cSub),10,mono);
UI_Label(UI_PREFIX+"AccL5",x1+ldx,rvy+line*4,"启动最大浮亏",cSub,10,uiFont); UI_Value(UI_PREFIX+"AccV5",x1+colW-14,rvy+line*4,"$"+DoubleToString(g_sessionMaxFloatLoss,2),cRed,9,mono);
string stText=(g_stDir==1?"多头 ↑" g_stDir==-1?"空头 ↓":"未确认"));
string allowText=(g_stDir==1?"只加 BUY" g_stDir==-1?"只加 SELL":"暂停新增"));
int trendLayers=(g_stDir==0?0:CountTrendPositions(g_stDir));
UI_Label(UI_PREFIX+"TrendH",x2+ldx,row2Y+12,"趋势状态",cText,13,uiFont);
UI_Label(UI_PREFIX+"TrendL1",x2+ldx,rvy,"Supertrend",cSub,10,uiFont); UI_Value(UI_PREFIX+"TrendV1",x2+colW-14,rvy,stText,(g_stDir==1?cGreen g_stDir==-1?cRed:cSub)),10,uiFont);
UI_Label(UI_PREFIX+"TrendL2",x2+ldx,rvy+line,"允许新增",cSub,10,uiFont); UI_Value(UI_PREFIX+"TrendV2",x2+colW-14,rvy+line,allowText,(g_stDir==1?cGreen g_stDir==-1?cRed:cSub)),10,uiFont);
UI_Label(UI_PREFIX+"TrendL3",x2+ldx,rvy+line*2,"趋势层数",cSub,10,uiFont); UI_Value(UI_PREFIX+"TrendV3",x2+colW-14,rvy+line*2,IntegerToString(trendLayers)+" / "+IntegerToString(顺势最大层数),cText,10,mono);
UI_Label(UI_PREFIX+"TrendL4",x2+ldx,rvy+line*3,"今日ST切换",cSub,10,uiFont); UI_Value(UI_PREFIX+"TrendV4",x2+colW-14,rvy+line*3,IntegerToString(g_todaySTFlips)+" 次",cBlue,10,mono);
UI_Label(UI_PREFIX+"TrendL5",x2+ldx,rvy+line*4,"ST数值",cSub,10,uiFont); UI_Value(UI_PREFIX+"TrendV5",x2+colW-14,rvy+line*4,(g_stValue>0?DoubleToString(g_stValue,_Digits):"-"),cSub,9,mono);
double oldLots=0,oldProfit=0,recoveryLots=0,recoveryProfit=0;
string oldSide="-";
int recoveryDir=0;
if(g_stDir==1)
{
oldSide="SELL"; oldLots=空单累计手数; oldProfit=空单累计利润; recoveryDir=-1;
recoveryLots=sellLockLots; recoveryProfit=sellLockProfit;
}
else if(g_stDir==-1)
{
oldSide="BUY"; oldLots=多单累计手数; oldProfit=多单累计利润; recoveryDir=1;
recoveryLots=buyLockLots; recoveryProfit=buyLockProfit;
}
bool hasOld=(oldLots>0.0);
bool recPending=(recoveryDir!=0 && HasRecoveryPending(recoveryDir));
double nextDist=0.0, nextModifyDist=0.0;
if(recoveryDir!=0) GetRecoveryDistances(nextDist,nextModifyDist);
UI_Label(UI_PREFIX+"LockH",x3+ldx,row2Y+12,"逆势恢复",cText,13,uiFont);
UI_Label(UI_PREFIX+"LockL1",x3+ldx,rvy,"旧方向",cSub,10,uiFont); UI_Value(UI_PREFIX+"LockV1",x3+colW-14,rvy,(hasOld?oldSide+" "+DoubleToString(oldLots,2):"无旧仓"),(oldSide=="BUY"?cGreen oldSide=="SELL"?cRed:cSub)),10,mono);
UI_Label(UI_PREFIX+"LockL2",x3+ldx,rvy+line,"恢复挂单",cSub,10,uiFont); UI_Value(UI_PREFIX+"LockV2",x3+colW-14,rvy+line,(recPending?"等待成交":"未挂"),(recPending?cOrange:cSub),10,uiFont);
UI_Label(UI_PREFIX+"LockL3",x3+ldx,rvy+line*2,"基准 / 层级",cSub,10,uiFont); UI_Value(UI_PREFIX+"LockV3",x3+colW-14,rvy+line*2,DoubleToString(g_recoveryBaseLots,2)+" / "+IntegerToString(g_recoveryAddLevel),cOrange,10,mono);
UI_Label(UI_PREFIX+"LockL4",x3+ldx,rvy+line*3,"下一档距离",cSub,10,uiFont); UI_Value(UI_PREFIX+"LockV4",x3+colW-14,rvy+line*3,DoubleToString(nextDist,0)+" pt",cText,10,mono);
UI_Label(UI_PREFIX+"LockL5",x3+ldx,rvy+line*4,"逆势组盈亏",cSub,9,uiFont); UI_Value(UI_PREFIX+"LockV5",x3+colW-14,rvy+line*4,"$"+DoubleToString(oldProfit,2),(oldProfit>=0?cGreen:cRed),9,mono);
if(启用ST顺势逆势恢复模式)
{
// ===== 第三排:顺势加仓状态 =====
int addY=row2Y+row2H+gap, addH=92;
UI_Rect(UI_PREFIX+"AddCard",x1,addY,innerW,addH,cCard2,cBorder);
string addDir=(g_stDir==1?"BUY" g_stDir==-1?"SELL":"-"));
string addState="等待趋势确认";
double extreme=0.0, pullPts=0.0, recoverPts=0.0;
if(g_stDir==1)
{
extreme=g_trendExtremeBuy;
pullPts=(extreme>0&&bid<extreme?(extreme-bid)/vPoint:0.0);
recoverPts=(g_buyPullbackArmed&&g_buyPullbackLow>0?(bid-g_buyPullbackLow)/vPoint:0.0);
if(trendLayers<=0)
addState=(g_trendRestartBlockBarBuy>0 && iTime(_Symbol,_Period,0)<=g_trendRestartBlockBarBuy ? "等待下一根K线重启" : "等待首多挂单/成交");
else if(trendLayers>=顺势最大层数) addState="趋势层数已满";
else if(g_buyPullbackArmed) addState="等待反弹确认";
else addState="等待回调";
}
else if(g_stDir==-1)
{
extreme=g_trendExtremeSell;
pullPts=(extreme>0&&ask>extreme?(ask-extreme)/vPoint:0.0);
recoverPts=(g_sellPullbackArmed&&g_sellPullbackHigh>0?(g_sellPullbackHigh-ask)/vPoint:0.0);
if(trendLayers<=0)
addState=(g_trendRestartBlockBarSell>0 && iTime(_Symbol,_Period,0)<=g_trendRestartBlockBarSell ? "等待下一根K线重启" : "等待首空挂单/成交");
else if(trendLayers>=顺势最大层数) addState="趋势层数已满";
else if(g_sellPullbackArmed) addState="等待回落确认";
else addState="等待反弹";
}
double lastSamePrice=0.0, currentGap=0.0;
datetime lastSameTime=0; ulong lastSameTicket=0;
bool hasLastSame=(g_stDir!=0 && GetLastSameDirectionEntry(g_stDir,lastSamePrice,lastSameTime,lastSameTicket));
double spacingCandidate=(g_stDir==1?ask g_stDir==-1?bid:0.0));
if(hasLastSame && spacingCandidate>0.0) currentGap=MathAbs(spacingCandidate-lastSamePrice);
double currentMinGap=(hasLastSame?MinSameDirectionSpacingPrice(lastSamePrice):0.0);
bool spacingOK=(!启用最小同向加仓间距 || !hasLastSame || currentGap>=currentMinGap);
double pullNeedPrice=TrendPullbackDistancePrice(g_stDir,(g_stDir==1?bid:ask));
double confirmNeedPrice=TrendRecoveryConfirmDistancePrice(g_stDir,(g_stDir==1?bid:ask));
double pullNowPrice=(g_stDir==1?(extreme>0&&bid<extreme?extreme-bid:0.0):(g_stDir==-1?(extreme>0&&ask>extreme?ask-extreme:0.0):0.0));
double confirmNowPrice=(g_stDir==1?(g_buyPullbackArmed&&g_buyPullbackLow>0?bid-g_buyPullbackLow:0.0):(g_stDir==-1?(g_sellPullbackArmed&&g_sellPullbackHigh>0?g_sellPullbackHigh-ask:0.0):0.0));
if(trendLayers>0 && !spacingOK && (g_buyPullbackArmed || g_sellPullbackArmed))
addState="等待最小价格间距";
UI_Label(UI_PREFIX+"AddH",x1+14,addY+10,"顺势加仓状态",cText,13,uiFont);
UI_Label(UI_PREFIX+"AddDir",x1+160,addY+12,"方向 "+addDir,(g_stDir==1?cGreen:(g_stDir==-1?cRed:cSub)),10,mono);
UI_Label(UI_PREFIX+"AddState",x1+300,addY+12,"状态 "+addState,cOrange,10,uiFont);
UI_Label(UI_PREFIX+"AddExtreme",x1+14,addY+42,"趋势极值 "+(extreme>0?DoubleToString(extreme,_Digits):"-"),cSub,10,mono);
string pullTxt=(顺势距离计算模式==ST_DISTANCE_PERCENT ? DoubleToString(pullNowPrice,2)+" / "+DoubleToString(pullNeedPrice,2)+" ("+DoubleToString(顺势回调百分比,3)+"%)" : DoubleToString(pullPts,0)+" / "+DoubleToString(固定顺势回调距离,0)+" pt");
string confirmTxt=(顺势距离计算模式==ST_DISTANCE_PERCENT ? DoubleToString(confirmNowPrice,2)+" / "+DoubleToString(confirmNeedPrice,2)+" ("+DoubleToString(顺势恢复确认百分比,3)+"%)" : DoubleToString(recoverPts,0)+" / "+DoubleToString(固定顺势恢复确认距离,0)+" pt");
UI_Label(UI_PREFIX+"AddPull",x1+245,addY+42,"回调 "+pullTxt,cText,9,mono);
UI_Label(UI_PREFIX+"AddConfirm",x1+470,addY+42,"恢复 "+confirmTxt,cText,9,mono);
UI_Label(UI_PREFIX+"AddToday",x1+14,addY+66,"今日逆势加仓 "+IntegerToString(g_todayRecoveryAdds)+" 次",cOrange,9,mono);
UI_Label(UI_PREFIX+"AddInterval",x1+245,addY+66,"时间间隔 "+IntegerToString(顺势最小加仓间隔秒)+" 秒",cSub,9,mono);
string gapTxt=(hasLastSame?DoubleToString(currentGap,2)+" / "+DoubleToString(currentMinGap,2)+(顺势距离计算模式==ST_DISTANCE_PERCENT?" ("+DoubleToString(最小同向加仓间距百分比,3)+"%)":""):"首仓不限制");
UI_Label(UI_PREFIX+"AddGap",x1+470,addY+66,"价格间距 "+gapTxt,(spacingOK?cGreen:cOrange),9,mono);
// ===== 第四排:均价 / Basket目标 =====
int priceY=addY+addH+gap, priceH=72;
UI_Rect(UI_PREFIX+" riceBar",x1,priceY,innerW,priceH,cCard2,cBorder);
string buyAvgTxt=(多单累计手数>0.0?DoubleToString(多单持仓均价,_Digits):"-");
string sellAvgTxt=(空单累计手数>0.0?DoubleToString(空单持仓均价,_Digits):"-");
string basketTpTxt=(g_basketTPPrice>0?DoubleToString(g_basketTPPrice,_Digits):"无唯一价格");
UI_Label(UI_PREFIX+" riceTitle",x1+14,priceY+8,"均价 / Basket退出",cText,12,uiFont);
UI_Label(UI_PREFIX+"BuyAvg",x1+14,priceY+35,"多均价 "+buyAvgTxt,cBlue,10,mono);
UI_Label(UI_PREFIX+"SellAvg",x1+220,priceY+35,"空均价 "+sellAvgTxt,cOrange,10,mono);
UI_Label(UI_PREFIX+"BasketTP",x1+430,priceY+35,"Basket TP价 "+basketTpTxt,cGreen,10,mono);
UI_Value(UI_PREFIX+"TPMode",x1+innerW-14,priceY+8,"目标 $"+DoubleToString(basketTarget,2),cSub,9,mono);
int statusY=priceY+priceH+gap, statusH=50;
UI_Rect(UI_PREFIX+"StatusBar",x1,statusY,innerW,statusH,cCard2,cBorder);
string status=(顺势距离计算模式==ST_DISTANCE_PERCENT
? "ST顺势 + 逆势快照恢复 | 百分比距离 回调 "+DoubleToString(顺势回调百分比,3)+"% | 恢复 "+DoubleToString(顺势恢复确认百分比,3)+"% | 硬间距 "+DoubleToString(最小同向加仓间距百分比,3)+"% | V:fanfans555"
: "ST顺势 + 逆势快照恢复 | 固定距离 回调 "+DoubleToString(固定顺势回调距离,0)+"pt | 恢复 "+DoubleToString(固定顺势恢复确认距离,0)+"pt | 硬间距 "+DoubleToString(固定最小同向加仓价格间距,2)+" | V:fanfans555");
UI_Label(UI_PREFIX+"Bottom",x1+14,statusY+16,status,cSub,9,uiFont);
if(手动全平) UI_Button("Close button","全部平仓",x0+W-134,statusY+8,120,34,C'132,41,45',clrWhite);
}
else
{
// 原V1.07模式保持紧凑显示
int priceY=row2Y+row2H+gap, priceH=68;
UI_Rect(UI_PREFIX+" riceBar",x1,priceY,innerW,priceH,cCard2,cBorder);
string buyAvgTxt=(多单累计手数>0.0?DoubleToString(多单持仓均价,_Digits):"-");
string sellAvgTxt=(空单累计手数>0.0?DoubleToString(空单持仓均价,_Digits):"-");
UI_Label(UI_PREFIX+" riceTitle",x1+14,priceY+8,"持仓均价 / 当前止盈位置",cText,11,uiFont);
UI_Label(UI_PREFIX+"BuyAvg",x1+14,priceY+36,"多均价 "+buyAvgTxt,cBlue,10,mono);
UI_Label(UI_PREFIX+"SellAvg",x1+220,priceY+36,"空均价 "+sellAvgTxt,cOrange,10,mono);
int statusY=priceY+priceH+gap;
UI_Rect(UI_PREFIX+"StatusBar",x1,statusY,innerW,50,cCard2,cBorder);
UI_Label(UI_PREFIX+"Bottom",x1+14,statusY+16,"V1.07原始逆势模式 | V:fanfans555",cSub,9,uiFont);
if(手动全平) UI_Button("Close button","全部平仓",x0+W-134,statusY+8,120,34,C'132,41,45',clrWhite);
}
DrawDailyStatsPanel(x0,y0,W);
Comment("");
}
//+------------------------------------------------------------------+
//| 创建按钮(保留兼容旧调用) |
//+------------------------------------------------------------------+
void sub_CreateButton(string obj_name, string object_text, int object_x_distance, int object_y_distance,
color object_BGcolor, color object_OPcolor, int object_corner_value, int object_XSIZE, int object_YSIZE)
{
UI_Button(obj_name,object_text,object_x_distance,object_y_distance,object_XSIZE,object_YSIZE,object_BGcolor,object_OPcolor);
}
//+------------------------------------------------------------------+
//| 图表事件处理 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_CLICK && sparam == UI_PREFIX+"StatsBtn")
{
g_statsPanelOpen=!g_statsPanelOpen;
if(!g_statsPanelOpen) DeleteStatsPanelObjects();
ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
Information();
ChartRedraw(0);
return;
}
if(id == CHARTEVENT_OBJECT_CLICK && sparam == "Close button")
{
runEA = false;
ExitAllTrades(clrCrimson, "手动平仓");
if(CountTrades("buy") == 0 && CountTrades("sell") == 0)
{
runEA = true;
Print("【手动全平】", Symbol(), " 全部持仓及挂单已处理");
}
ObjectSetInteger(0,"Close button",OBJPROP_STATE,false);
Information();
ChartRedraw(0);
}
}
//+------------------------------------------------------------------+
//| 平仓所有订单,且删除挂单 |
//+------------------------------------------------------------------+
void ExitAllTrades(color Color, string reason)
{
// 平仓所有持仓
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionInfo.SelectByIndex(i))
{
if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == magicNumber)
{
Trade.PositionClose(PositionInfo.Ticket());
}
}
}
// 删除所有挂单
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderInfo.SelectByIndex(i))
{
if(OrderInfo.Symbol() == _Symbol && OrderInfo.Magic() == magicNumber)
{
Trade.OrderDelete(OrderInfo.Ticket());
}
}
}
Print("Closed all positions because ", reason);
}
//+------------------------------------------------------------------+
//| 计算当前订单数 |
//+------------------------------------------------------------------+
int CountTrades(string type)
{
int count = 0;
if(type == "buy")
{
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionInfo.SelectByIndex(i))
{
if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == magicNumber &&
PositionInfo.PositionType() == POSITION_TYPE_BUY)
count++;
}
}
}
else if(type == "sell")
{
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionInfo.SelectByIndex(i))
{
if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == magicNumber &&
PositionInfo.PositionType() == POSITION_TYPE_SELL)
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| 亏损达到锁仓 |
//+------------------------------------------------------------------+
void 亏损达到锁仓()
{
double currentProfit = 多单累计利润 + 空单累计利润;
if(currentProfit <= 浮亏锁仓金额)
{
runEA = false;
runEAG = true;
Alert(Symbol() + "锁仓停止交易");
}
}
//+------------------------------------------------------------------+
//| 解锁 |
//+------------------------------------------------------------------+
void suo()
{
double currentProfit = 多单累计利润 + 空单累计利润;
if(runEAG)
{
if(currentProfit >= 0)
{
ExitAllTrades(clrRed, "解锁成功继续交易!");
runEA = true;
runEAG = false;
}
else if(currentProfit < 止损金额)
{
ExitAllTrades(clrRed, "触发止损!");
runEA = true;
runEAG = false;
}
}
}
//+------------------------------------------------------------------+
|
|