# 机构级缠论 EA 开发文档(MT4 模拟交易版)-2
//+------------------------------------------------------------------+
//| chan_structure_sim.mq4 |
//| 模拟机构级缠论结构与回踩提示(仅模拟,不下单) |
//+------------------------------------------------------------------+
property strict
property copyright "Generated with assistance from Codex"
property link "https://example.com"
property version "1.000"
property description "基于开发文档的模拟缠论结构 EA。仅绘图与日志,不执行真实下单。"
//--- 输入参数
input ENUM_TIMEFRAMES InpPrimaryTF = PERIOD_H1; // 主结构周期
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_H4; // 上级确认周期
input int InpFractalDepth = 2; // 分型确认左右K数
input int InpMaxRecords = 500; // 结构最大缓存
input double InpSimLots = 0.01; // 模拟每笔手数
input color InpPremiumColor = clrTomato; // 溢价区颜色
input color InpDiscountColor = clrLightGreen;// 折价区颜色
input color InpLiquidityColor= clrDeepSkyBlue;// 流动性清扫区颜色
input double InpLiquidityPad = 5.0; // 清扫区延伸点数
input bool InpEnableLogging = true; // 写入日志文件
input bool InpShowDevelopingZhongshu = true;// 显示进行中中枢
//--- 常量
define INVALID_INDEX (-1)
define OBJPREFIX "ChanEA"
//--- 结构体
struct FractalPoint
{
datetime time;
double price;
bool isHigh;
int shift;
};
struct Pen
{
FractalPoint start;
FractalPoint end;
double high;
double low;
bool confirmed;
};
struct ZhongShu
{
Pen a;
Pen b;
Pen c;
double zg;
double zd;
datetime startTime;
datetime endTime;
bool locked;
};
struct SimTrade
{
string id;
datetime openTime;
double entry;
double stop;
double target;
double lots;
bool isLong;
string reason;
int state; // 0 open, 1 closed
};
struct TrendInfo
{
ENUM_TIMEFRAMES tf;
string highStatus;
string lowStatus;
string trendState;
bool hasZone;
bool developing;
ZhongShu zone;
double longSL;
double shortSL;
string sweepStrength;
string rrHint;
};
//--- 全局变量
int g_digits = 0;
double g_point = 0.0;
datetime g_lastPrimaryBar = 0;
datetime g_lastHigherBar = 0;
FractalPoint g_fractals[];
Pen g_pens[];
ZhongShu g_zhongshus[];
SimTrade g_trades[];
int g_logHandle = INVALID_HANDLE;
string g_logFile = "";
ZhongShu g_developingZone;
bool g_hasDevelopingZone = false;
define GV_SYNC_ZG "CHANEA_SYNC_ZG"
define GV_SYNC_ZD "CHANEA_SYNC_ZD"
define GV_SYNC_LONGSL "CHANEA_SYNC_LONGSL"
define GV_SYNC_SHORTSL "CHANEA_SYNC_SHORTSL"
define GV_SYNC_TIME "CHANEA_SYNC_TIME"
define GV_SYNC_DEV "CHANEA_SYNC_DEV"
//--- 函数声明
void InitializeCollections();
void CloseLog();
bool PrepareLog();
void WriteLog(string text);
bool CheckNewBar(ENUM_TIMEFRAMES tf, datetime &lastTime);
void ProcessPrimary();
void UpdateFractals(ENUM_TIMEFRAMES tf);
bool IsFractal(ENUM_TIMEFRAMES tf, int shift, bool up);
void AppendFractal(FractalPoint &point);
void BuildPens();
void TryCreateZhongshu();
void DrawVisuals();
void DrawZhongshu(const ZhongShu &zone, int index);
string ObjName(string suffix, int index);
void CleanupObjects();
double PointsToPrice(double points);
int DisplayDigits();
string PriceToStr(double value);
string ShortStatusTag(const string status);
void GetStructureStatus(string &highStatus, string &lowStatus);
bool ComputeDevelopingZone(ZhongShu &zone);
void DrawDevelopingZone(const ZhongShu &zone);
void ClearDevelopingZoneObjects();
void UpdateSimulation();
void HighlightDiscountRetest(const ZhongShu &zone, int index);
int FindOpenTradeIndex(string id);
void OpenSimTrade(const ZhongShu &zone, bool isLong, double entryPrice, double pad);
void CloseTrade(int index, bool hitTarget, datetime closeTime);
void UpdateTradePanel();
void UpdateTradeOverlayText(string text);
void UpdateStructureOverlayText(string text);
bool CollectFractalsTF(ENUM_TIMEFRAMES tf, FractalPoint &arr[], int &count, int maxBars);
void AppendFractalLocal(FractalPoint &point, FractalPoint &arr[], int &count);
void BuildPensFromFractals(FractalPoint &arr[], int count, Pen &pens[], int &penCount);
bool FindLatestZhongshuLocal(Pen &pens[], int penCount, ZhongShu &zone);
bool ComputeDevelopingZoneFromData(Pen &pens[], int penCount, FractalPoint &fracs[], int fracCount, ENUM_TIMEFRAMES tf, ZhongShu &zone);
bool ComputeTrendInfo(ENUM_TIMEFRAMES tf, TrendInfo &info);
string TFToString(ENUM_TIMEFRAMES tf);
void AppendTrendPanelLine(string label, TrendInfo &info, string &panel);
void ShareKeyLevelsForM15(TrendInfo &info);
void DrawSharedKeyLevels();
color DetermineOverlayColor();
void ApplyOverlayLabelStyle(const string name, ENUM_BASE_CORNER corner, int xDist, int yDist, int fontSize);
int ColorWithAlpha(color base, int alpha);
void EnsureHLine(string name, double price, color clr, ENUM_LINE_STYLE style, int width);
void TrimFractals();
void TrimPens();
void TrimZhongshus();
void TrimTrades();
bool BuildFallbackZoneFromFractals(FractalPoint &fracs[], int count, ZhongShu &zone);
//+------------------------------------------------------------------+
int OnInit()
{
g_digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
g_point = MarketInfo(Symbol(), MODE_POINT);
InitializeCollections();
if(InpEnableLogging && !PrepareLog())
{
Print(FUNCTION, ": 无法创建日志文件。");
}
CleanupObjects();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Comment("");
CloseLog();
CleanupObjects();
ArrayResize(g_fractals, 0);
ArrayResize(g_pens, 0);
ArrayResize(g_zhongshus, 0);
ArrayResize(g_trades, 0);
}
//+------------------------------------------------------------------+
void OnTick()
{
if(CheckNewBar(InpPrimaryTF, g_lastPrimaryBar))
{
ProcessPrimary();
}
}
//+------------------------------------------------------------------+
void InitializeCollections()
{
ArrayResize(g_fractals, 0);
ArrayResize(g_pens, 0);
ArrayResize(g_zhongshus, 0);
ArrayResize(g_trades, 0);
g_hasDevelopingZone = false;
}
//+------------------------------------------------------------------+
bool PrepareLog()
{
g_logFile = StringFormat("%s_chan_sim_log.csv", TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
g_logHandle = FileOpen(g_logFile, FILE_WRITE|FILE_CSV|FILE_SHARE_WRITE, ';');
if(g_logHandle == INVALID_HANDLE)
return(false);
FileWrite(g_logHandle, "time", "type", "message");
FileFlush(g_logHandle);
return(true);
}
//+------------------------------------------------------------------+
void CloseLog()
{
if(g_logHandle != INVALID_HANDLE)
{
FileFlush(g_logHandle);
FileClose(g_logHandle);
g_logHandle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
void WriteLog(string text)
{
if(!InpEnableLogging || g_logHandle == INVALID_HANDLE)
return;
FileWrite(g_logHandle, TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES), "INFO", text);
}
//+------------------------------------------------------------------+
bool CheckNewBar(ENUM_TIMEFRAMES tf, datetime &lastTime)
{
datetime current = iTime(Symbol(), tf, 0);
if(current == 0)
return(false);
if(current != lastTime)
{
lastTime = current;
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
void ProcessPrimary()
{
UpdateFractals(InpPrimaryTF);
BuildPens();
TryCreateZhongshu();
DrawVisuals();
UpdateSimulation();
}
//+------------------------------------------------------------------+
void UpdateFractals(ENUM_TIMEFRAMES tf)
{
int totalBars = iBars(Symbol(), tf);
if(totalBars <= InpFractalDepth * 2 + 1)
return;
int processed = 0;
for(int shift = totalBars - (InpFractalDepth + 2); shift >= InpFractalDepth; --shift)
{
if(processed >= 500)
break;
bool isHigh = IsFractal(tf, shift, true);
bool isLow = IsFractal(tf, shift, false);
if(isHigh || isLow)
{
datetime fTime = iTime(Symbol(), tf, shift);
double fPrice= isHigh ? iHigh(Symbol(), tf, shift) : iLow(Symbol(), tf, shift);
// 检查是否已存在
bool exists=false;
int total = ArraySize(g_fractals);
for(int i=0;i<total;++i)
{
if(g_fractals[i].time == fTime && g_fractals[i].isHigh == isHigh)
{
exists = true;
break;
}
}
if(exists)
continue;
FractalPoint fp;
fp.time = fTime;
fp.price = fPrice;
fp.isHigh = isHigh;
fp.shift = shift;
AppendFractal(fp);
processed++;
}
}
}
//+------------------------------------------------------------------+
bool IsFractal(ENUM_TIMEFRAMES tf, int shift, bool up)
{
for(int i=1; i<=InpFractalDepth; ++i)
{
double current = up ? iHigh(Symbol(), tf, shift) : iLow(Symbol(), tf, shift);
double compareLeft = up ? iHigh(Symbol(), tf, shift+i) : iLow(Symbol(), tf, shift+i);
double compareRight = up ? iHigh(Symbol(), tf, shift-i) : iLow(Symbol(), tf, shift-i);
if(up)
{
if(current <= compareLeft || current <= compareRight)
return(false);
}
else
{
if(current >= compareLeft || current >= compareRight)
return(false);
}
}
return(true);
}
//+------------------------------------------------------------------+
void AppendFractal(FractalPoint &point)
{
int total = ArraySize(g_fractals);
if(total > 0)
{
FractalPoint last = g_fractals[total-1];
if(last.isHigh == point.isHigh)
{
if(point.isHigh && point.price > last.price)
{
g_fractals[total-1] = point;
}
else if(!point.isHigh && point.price < last.price)
{
g_fractals[total-1] = point;
}
return;
}
}
ArrayResize(g_fractals, total + 1);
g_fractals[total] = point;
TrimFractals();
}
//+------------------------------------------------------------------+
void BuildPens()
{
int total = ArraySize(g_fractals);
if(total < 2)
{
ArrayResize(g_pens, 0);
return;
}
ArrayResize(g_pens, total-1);
for(int i=0; i<total-1; ++i)
{
g_pens[i].start = g_fractals[i];
g_pens[i].end = g_fractals[i+1];
g_pens[i].high = MathMax(g_fractals[i].price, g_fractals[i+1].price);
g_pens[i].low = MathMin(g_fractals[i].price, g_fractals[i+1].price);
g_pens[i].confirmed = true;
}
TrimPens();
}
//+------------------------------------------------------------------+
void TryCreateZhongshu()
{
int penTotal = ArraySize(g_pens);
if(penTotal < 3)
{
ArrayResize(g_zhongshus, 0);
return;
}
ArrayResize(g_zhongshus, 0);
for(int i=0; i<penTotal-2; ++i)
{
double zg = MathMin(MathMin(g_pens[i].high, g_pens[i+1].high), g_pens[i+2].high);
double zd = MathMax(MathMax(g_pens[i].low, g_pens[i+1].low), g_pens[i+2].low);
if(zg <= zd)
continue; // 无重叠
int idx = ArraySize(g_zhongshus);
ArrayResize(g_zhongshus, idx+1);
g_zhongshus[idx].a = g_pens[i];
g_zhongshus[idx].b = g_pens[i+1];
g_zhongshus[idx].c = g_pens[i+2];
g_zhongshus[idx].zg = zg;
g_zhongshus[idx].zd = zd;
g_zhongshus[idx].startTime = g_pens[i].start.time;
g_zhongshus[idx].endTime = g_pens[i+2].end.time;
g_zhongshus[idx].locked = true;
}
TrimZhongshus();
}
//+------------------------------------------------------------------+
void DrawVisuals()
{
CleanupObjects();
for(int i=0; i<ArraySize(g_zhongshus); ++i)
{
DrawZhongshu(g_zhongshus[i], i);
HighlightDiscountRetest(g_zhongshus[i], i);
}
ClearDevelopingZoneObjects();
g_hasDevelopingZone = false;
if(ComputeDevelopingZone(g_developingZone))
{
DrawDevelopingZone(g_developingZone);
g_hasDevelopingZone = true;
}
DrawSharedKeyLevels();
}
//+------------------------------------------------------------------+
void DrawZhongshu(const ZhongShu &zone, int index)
{
datetime tStart = zone.startTime;
datetime tEnd = zone.endTime;
// 中枢框架(仅边线)
string rectName = ObjName("ZSFRAME", index);
ObjectCreate(0, rectName, OBJ_RECTANGLE, 0, tStart, zone.zg, tEnd, zone.zd);
ObjectSetInteger(0, rectName, OBJPROP_COLOR, clrGray);
ObjectSetInteger(0, rectName, OBJPROP_BACK, true);
ObjectSetInteger(0, rectName, OBJPROP_FILL, false);
ObjectSetInteger(0, rectName, OBJPROP_WIDTH, 1);
// 中枢上沿线
string upperName = ObjName("ZSUPPER", index);
ObjectCreate(0, upperName, OBJ_TREND, 0, tStart, zone.zg, tEnd, zone.zg);
ObjectSetInteger(0, upperName, OBJPROP_COLOR, clrDimGray);
ObjectSetInteger(0, upperName, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, upperName, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, upperName, OBJPROP_RAY, false);
// 中枢下沿线
string lowerName = ObjName("ZSLOWER", index);
ObjectCreate(0, lowerName, OBJ_TREND, 0, tStart, zone.zd, tEnd, zone.zd);
ObjectSetInteger(0, lowerName, OBJPROP_COLOR, clrDimGray);
ObjectSetInteger(0, lowerName, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, lowerName, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, lowerName, OBJPROP_RAY, false);
// 中枢中线
double midPrice = (zone.zg + zone.zd) / 2.0;
string midName = ObjName("ZSMID", index);
ObjectCreate(0, midName, OBJ_TREND, 0, tStart, midPrice, tEnd, midPrice);
ObjectSetInteger(0, midName, OBJPROP_COLOR, clrSilver);
ObjectSetInteger(0, midName, OBJPROP_STYLE, STYLE_DOT);
ObjectSetInteger(0, midName, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, midName, OBJPROP_RAY, false);
double padPrice = PointsToPrice(InpLiquidityPad);
// 溢价警示线
string premiumLine = ObjName("ZSPREMLINE", index);
ObjectCreate(0, premiumLine, OBJ_TREND, 0, tStart, zone.zg + padPrice, tEnd, zone.zg + padPrice);
ObjectSetInteger(0, premiumLine, OBJPROP_COLOR, InpPremiumColor);
ObjectSetInteger(0, premiumLine, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, premiumLine, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, premiumLine, OBJPROP_RAY, false);
// 折价警示线
string discountLine = ObjName("ZSDISCLINE", index);
ObjectCreate(0, discountLine, OBJ_TREND, 0, tStart, zone.zd - padPrice, tEnd, zone.zd - padPrice);
ObjectSetInteger(0, discountLine, OBJPROP_COLOR, InpDiscountColor);
ObjectSetInteger(0, discountLine, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, discountLine, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, discountLine, OBJPROP_RAY, false);
datetime midTime = tStart + (tEnd - tStart) / 2;
// 溢价区块
string premiumBox = ObjName("ZSPREMBOX", index);
ObjectCreate(0, premiumBox, OBJ_RECTANGLE, 0, tStart, zone.zg + padPrice, tEnd, zone.zg);
ObjectSetInteger(0, premiumBox, OBJPROP_COLOR, ColorWithAlpha(InpPremiumColor, 80));
ObjectSetInteger(0, premiumBox, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, premiumBox, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, premiumBox, OBJPROP_FILL, true);
ObjectSetInteger(0, premiumBox, OBJPROP_BACK, false);
// 折价区块
string discountBox = ObjName("ZSDISCBOX", index);
ObjectCreate(0, discountBox, OBJ_RECTANGLE, 0, tStart, zone.zd, tEnd, zone.zd - padPrice);
ObjectSetInteger(0, discountBox, OBJPROP_COLOR, ColorWithAlpha(InpDiscountColor, 80));
ObjectSetInteger(0, discountBox, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, discountBox, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, discountBox, OBJPROP_FILL, true);
ObjectSetInteger(0, discountBox, OBJPROP_BACK, false);
// 溢价标签
string premiumTag = ObjName("ZSPREMTAG", index);
ObjectCreate(0, premiumTag, OBJ_TEXT, 0, midTime, zone.zg + padPrice * 0.5);
ObjectSetString(0, premiumTag, OBJPROP_TEXT, "溢价");
ObjectSetInteger(0, premiumTag, OBJPROP_COLOR, InpPremiumColor);
ObjectSetInteger(0, premiumTag, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, premiumTag, OBJPROP_BACK, false);
ObjectSetInteger(0, premiumTag, OBJPROP_ANCHOR, ANCHOR_CENTER);
// 折价标签
string discountTag = ObjName("ZSDISCTAG", index);
ObjectCreate(0, discountTag, OBJ_TEXT, 0, midTime, zone.zd - padPrice * 0.5);
ObjectSetString(0, discountTag, OBJPROP_TEXT, "折价");
ObjectSetInteger(0, discountTag, OBJPROP_COLOR, InpDiscountColor);
ObjectSetInteger(0, discountTag, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, discountTag, OBJPROP_BACK, false);
ObjectSetInteger(0, discountTag, OBJPROP_ANCHOR, ANCHOR_CENTER);
// 标注文本
string labelName = ObjName("ZSLABEL", index);
ObjectCreate(0, labelName, OBJ_TEXT, 0, midTime, zone.zg);
ObjectSetString(0, labelName, OBJPROP_TEXT, StringFormat("ZS #%d", index+1));
ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, labelName, OBJPROP_BACK, false);
}
//+------------------------------------------------------------------+
void HighlightDiscountRetest(const ZhongShu &zone, int index)
{
// 监控最新价格是否在折价区内回踩
double price = iClose(Symbol(), InpPrimaryTF, 1);
if(price <= zone.zd && price >= zone.zd - PointsToPrice(InpLiquidityPad))
{
string infoName = ObjName("ZSDISCALERT", index);
ObjectCreate(0, infoName, OBJ_TEXT, 0, TimeCurrent(), zone.zd - PointsToPrice(InpLiquidityPad) );
ObjectSetString(0, infoName, OBJPROP_TEXT, "折价回踩确认");
ObjectSetInteger(0, infoName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, infoName, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, infoName, OBJPROP_BACK, false);
string zoneName = ObjName("ZSDISC_HIGHLIGHT_", index);
ObjectCreate(0, zoneName, OBJ_RECTANGLE, 0, zone.startTime, zone.zd, TimeCurrent(), zone.zd - PointsToPrice(InpLiquidityPad));
ObjectSetInteger(0, zoneName, OBJPROP_COLOR, InpLiquidityColor);
ObjectSetInteger(0, zoneName, OBJPROP_FILL, true);
WriteLog("折价区回踩提示触发。");
}
}
//+------------------------------------------------------------------+
void CleanupObjects()
{
int total = ObjectsTotal();
for(int i=total-1; i>=0; --i)
{
string name = ObjectName(0, i);
if(StringFind(name, OBJPREFIX) == 0 ||
StringFind(name, "DEV") == 0 ||
StringFind(name, "FRAC_HIGH") >= 0 ||
StringFind(name, "FRAC_LOW") >= 0)
{
ObjectDelete(0, name);
}
}
}
//+------------------------------------------------------------------+
string ObjName(string suffix, int index)
{
return(OBJ_PREFIX + suffix + IntegerToString(index));
}
//+------------------------------------------------------------------+
double PointsToPrice(double points)
{
return(points * g_point);
}
//+------------------------------------------------------------------+
void UpdateSimulation()
{
int zsTotal = ArraySize(g_zhongshus);
if(zsTotal == 0)
return;
double padPrice = PointsToPrice(InpLiquidityPad);
double closePrice = iClose(Symbol(), InpPrimaryTF, 1);
double barHigh = iHigh(Symbol(), InpPrimaryTF, 1);
double barLow = iLow(Symbol(), InpPrimaryTF, 1);
datetime barTime = iTime(Symbol(), InpPrimaryTF, 1);
// 检查每个中枢是否触发新的模拟交易
for(int i=0; i<zsTotal; ++i)
{
ZhongShu zone = gzhongshus[i];
string longId = StringFormat("%I64u%I64ulong", (long)zone.startTime, (long)zone.endTime);
string shortId = StringFormat("%I64u%I64u_short", (long)zone.startTime, (long)zone.endTime);
// 折价区回踩做多
if(closePrice <= zone.zd && closePrice >= zone.zd - padPrice && FindOpenTradeIndex(longId) == -1)
{
OpenSimTrade(zone, true, closePrice, padPrice);
}
// 溢价区回踩做空
if(closePrice >= zone.zg && closePrice <= zone.zg + padPrice && FindOpenTradeIndex(shortId) == -1)
{
OpenSimTrade(zone, false, closePrice, padPrice);
}
}
// 检查已有模拟仓位的止盈止损
for(int t=0; t<ArraySize(g_trades); ++t)
{
if(g_trades[t].state != 0)
continue;
if(g_trades[t].isLong)
{
if(barLow <= g_trades[t].stop)
{
CloseTrade(t, false, barTime);
}
else if(barHigh >= g_trades[t].target)
{
CloseTrade(t, true, barTime);
}
}
else
{
if(barHigh >= g_trades[t].stop)
{
CloseTrade(t, false, barTime);
}
else if(barLow <= g_trades[t].target)
{
CloseTrade(t, true, barTime);
}
}
}
UpdateTradePanel();
}
//+------------------------------------------------------------------+
int FindOpenTradeIndex(string id)
{
for(int i=0; i<ArraySize(g_trades); ++i)
{
if(g_trades[i].id == id && g_trades[i].state == 0)
return(i);
}
return(-1);
}
//+------------------------------------------------------------------+
void OpenSimTrade(const ZhongShu &zone, bool isLong, double entryPrice, double pad)
{
SimTrade trade;
trade.id = StringFormat("%I64u%I64u%s", (long)zone.startTime, (long)zone.endTime, isLong ? "long" : "short");
trade.openTime = iTime(Symbol(), InpPrimaryTF, 1);
trade.entry = entryPrice;
trade.lots = InpSimLots;
trade.isLong = isLong;
trade.state = 0;
trade.reason = isLong ? "折价回踩买入" : "溢价回踩卖出";
if(isLong)
trade.stop = zone.zd - pad;
else
trade.stop = zone.zg + pad;
if(isLong)
trade.target = trade.entry + 2.0 (trade.entry - trade.stop);
else
trade.target = trade.entry - 2.0 (trade.stop - trade.entry);
int idx = ArraySize(g_trades);
ArrayResize(g_trades, idx + 1);
g_trades[idx] = trade;
TrimTrades();
WriteLog(StringFormat("开仓: %s entry=%.5f stop=%.5f target=%.5f lots=%.2f", trade.reason, trade.entry, trade.stop, trade.target, trade.lots));
}
//+------------------------------------------------------------------+
void CloseTrade(int index, bool hitTarget, datetime closeTime)
{
g_trades[index].state = 1;
string result = hitTarget ? "止盈" : "止损";
WriteLog(StringFormat("平仓: %s result=%s entry=%.5f stop=%.5f target=%.5f", g_trades[index].reason, result, g_trades[index].entry, g_trades[index].stop, g_trades[index].target));
UpdateTradePanel();
;
}
//+------------------------------------------------------------------+
void UpdateTradePanel()
{
string structure = "";
string overlay = "模拟仓位信息:\n";
bool hasOpen = false;
for(int t=0; t<ArraySize(g_trades); ++t)
{
if(g_trades[t].state != 0)
continue;
hasOpen = true;
overlay += StringFormat("%s | 手数 %.2f | 入场 %.5f | 止损 %.5f | 止盈 %.5f | 开仓 %s\n",
g_trades[t].isLong ? "多头" : "空头",
g_trades[t].lots,
g_trades[t].entry,
g_trades[t].stop,
g_trades[t].target,
TimeToString(g_trades[t].openTime, TIME_DATE|TIME_MINUTES));
}
if(!hasOpen)
overlay += "无未平仓模拟单。\n";
string highStatus, lowStatus;
GetStructureStatus(highStatus, lowStatus);
string highTag = ShortStatusTag(highStatus);
string lowTag = ShortStatusTag(lowStatus);
structure += StringFormat("结构 顶%s 底%s\n", highTag, lowTag);
string trendState = "震荡";
if(StringFind(highStatus, "HH") == 0 && StringFind(lowStatus, "HL") == 0)
trendState = "上行";
else if(StringFind(highStatus, "LH") == 0 && StringFind(lowStatus, "LL") == 0)
trendState = "下行";
structure += StringFormat("趋势 %s\n", trendState);
ZhongShu refZone;
bool haveZone = false;
ClearZhongShuStruct(refZone);
if(g_hasDevelopingZone)
{
refZone = g_developingZone;
haveZone = true;
}
else if(ArraySize(g_zhongshus) > 0)
{
refZone = g_zhongshus[ArraySize(g_zhongshus)-1];
haveZone = true;
}
if(haveZone)
{
double longSL = refZone.zd - PointsToPrice(InpLiquidityPad);
double shortSL= refZone.zg + PointsToPrice(InpLiquidityPad);
string zoneFlag = refZone.locked ? "" : "*";
structure += StringFormat("区间 %s-%s%s\n", PriceToStr(refZone.zd), PriceToStr(refZone.zg), zoneFlag);
structure += StringFormat("SL 多≤%s 空≥%s\n", PriceToStr(longSL), PriceToStr(shortSL));
overlay += StringFormat("订单流基准: zg %.5f / zd %.5f\n", refZone.zg, refZone.zd);
overlay += StringFormat("多头SL≤%.5f | 空头SL≥%.5f\n", longSL, shortSL);
overlay += "目标提示: 维持 1:2 盈亏比\n";
}
structure += "周期:\n";
TrendInfo infoHigher, infoPrimary, infoLower;
InitTrendInfo(infoHigher, InpHigherTF);
InitTrendInfo(infoPrimary, InpPrimaryTF);
InitTrendInfo(infoLower, PERIOD_M15);
bool infoHigherOK = ComputeTrendInfo(InpHigherTF, infoHigher);
bool infoPrimaryOK = ComputeTrendInfo(InpPrimaryTF, infoPrimary);
bool infoLowerOK = ComputeTrendInfo(PERIOD_M15, infoLower);
if(infoHigherOK) AppendTrendPanelLine(TFToString(InpHigherTF), infoHigher, structure);
if(infoPrimaryOK) AppendTrendPanelLine(TFToString(InpPrimaryTF), infoPrimary, structure);
if(infoLowerOK) AppendTrendPanelLine(TFToString(PERIOD_M15), infoLower, structure);
if(Period() == InpPrimaryTF)
{
if(infoPrimaryOK)
ShareKeyLevelsForM15(infoPrimary);
else
{
TrendInfo dummy; InitTrendInfo(dummy, InpPrimaryTF);
ShareKeyLevelsForM15(dummy);
}
}
Comment("");
UpdateTradeOverlayText(overlay);
UpdateStructureOverlayText(structure);
}
//+------------------------------------------------------------------+
void TrimFractals()
{
int total = ArraySize(g_fractals);
if(total <= InpMaxRecords)
return;
int keep = InpMaxRecords;
int start = total - keep;
for(int i=0; i<keep; ++i)
g_fractals[i] = g_fractals[start + i];
ArrayResize(g_fractals, keep);
}
//+------------------------------------------------------------------+
void TrimPens()
{
int total = ArraySize(g_pens);
if(total <= InpMaxRecords)
return;
int keep = InpMaxRecords;
int start = total - keep;
for(int i=0; i<keep; ++i)
g_pens[i] = g_pens[start + i];
ArrayResize(g_pens, keep);
}
//+------------------------------------------------------------------+
void TrimZhongshus()
{
int total = ArraySize(g_zhongshus);
if(total <= InpMaxRecords)
return;
int keep = InpMaxRecords;
int start = total - keep;
for(int i=0; i<keep; ++i)
g_zhongshus[i] = g_zhongshus[start + i];
ArrayResize(g_zhongshus, keep);
}
//+------------------------------------------------------------------+
void TrimTrades()
{
int total = ArraySize(g_trades);
if(total <= InpMaxRecords)
return;
int keep = InpMaxRecords;
int start = total - keep;
for(int i=0; i<keep; ++i)
g_trades[i] = g_trades[start + i];
ArrayResize(g_trades, keep);
}
//+------------------------------------------------------------------+
void ClearFractalPoint(FractalPoint &fp)
{
fp.time = 0;
fp.price = 0.0;
fp.isHigh = false;
fp.shift = 0;
}
//+------------------------------------------------------------------+
void ClearPenStruct(Pen &pen)
{
ClearFractalPoint(pen.start);
ClearFractalPoint(pen.end);
pen.high = 0.0;
pen.low = 0.0;
pen.confirmed = false;
}
//+------------------------------------------------------------------+
void ClearZhongShuStruct(ZhongShu &zone)
{
ClearPenStruct(zone.a);
ClearPenStruct(zone.b);
ClearPenStruct(zone.c);
zone.zg = 0.0;
zone.zd = 0.0;
zone.startTime = 0;
zone.endTime = 0;
zone.locked = false;
}
//+------------------------------------------------------------------+
void InitTrendInfo(TrendInfo &info, ENUM_TIMEFRAMES tf)
{
info.tf = tf;
info.highStatus = "不足数据";
info.lowStatus = "不足数据";
info.trendState = "未知";
info.hasZone = false;
info.developing = false;
info.longSL = 0.0;
info.shortSL = 0.0;
info.sweepStrength = "无结构";
info.rrHint = "";
ClearZhongShuStruct(info.zone);
}
//+------------------------------------------------------------------+
void GetStructureStatus(string &highStatus, string &lowStatus)
{
highStatus = "不足数据";
lowStatus = "不足数据";
int total = ArraySize(g_fractals);
if(total < 2)
return;
double highVals[2]={0.0,0.0};
double lowVals[2]={0.0,0.0};
int highCount = 0;
int lowCount = 0;
for(int i=total-1; i>=0 && (highCount<2 || lowCount<2); --i)
{
if(g_fractals[i].isHigh && highCount < 2)
{
highVals[highCount] = g_fractals[i].price;
highCount++;
}
else if(!g_fractals[i].isHigh && lowCount < 2)
{
lowVals[lowCount] = g_fractals[i].price;
lowCount++;
}
}
if(highCount == 2)
{
if(highVals[0] > highVals[1])
highStatus = StringFormat("HH (%.5f > %.5f)", highVals[0], highVals[1]);
else if(highVals[0] < highVals[1])
highStatus = StringFormat("LH (%.5f < %.5f)", highVals[0], highVals[1]);
else
highStatus = StringFormat("Equal (%.5f)", highVals[0]);
}
if(lowCount == 2)
{
if(lowVals[0] < lowVals[1])
lowStatus = StringFormat("LL (%.5f < %.5f)", lowVals[0], lowVals[1]);
else if(lowVals[0] > lowVals[1])
lowStatus = StringFormat("HL (%.5f > %.5f)", lowVals[0], lowVals[1]);
else
lowStatus = StringFormat("Equal (%.5f)", lowVals[0]);
}
}
//+------------------------------------------------------------------+
int DisplayDigits()
{
int digits = g_digits;
if(digits <= 0)
digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
if(digits > 4)
digits = 4;
return(digits);
}
//+------------------------------------------------------------------+
string PriceToStr(double value)
{
return(DoubleToString(value, DisplayDigits()));
}
//+------------------------------------------------------------------+
string ShortStatusTag(const string status)
{
if(status == "" || StringFind(status, "不足数据") == 0)
return("NA");
if(StringFind(status, "Equal") == 0)
return("EQ");
string tag = StringSubstr(status, 0, 2);
return(StringToUpper(tag));
}
//+------------------------------------------------------------------+
bool ComputeDevelopingZone(ZhongShu &zone)
{
if(!InpShowDevelopingZhongshu)
return(false);
int penTotal = ArraySize(g_pens);
int fracTotal = ArraySize(g_fractals);
if(penTotal < 2 || fracTotal < 1)
return(false);
Pen penA = g_pens[penTotal-2];
Pen penB = g_pens[penTotal-1];
FractalPoint lastFrac = g_fractals[fracTotal-1];
double livePrice = iClose(Symbol(), InpPrimaryTF, 0);
Pen penC;
penC.start = lastFrac;
penC.end.time = TimeCurrent();
penC.end.price = livePrice;
penC.high = MathMax(lastFrac.price, livePrice);
penC.low = MathMin(lastFrac.price, livePrice);
penC.confirmed = false;
double zg = MathMin(MathMin(penA.high, penB.high), penC.high);
double zd = MathMax(MathMax(penA.low, penB.low), penC.low);
if(zg <= zd)
{
if(BuildFallbackZoneFromFractals(g_fractals, ArraySize(g_fractals), zone))
return(true);
return(false);
}
zone.a = penA;
zone.b = penB;
zone.c = penC;
zone.zg = zg;
zone.zd = zd;
zone.startTime = penA.start.time;
zone.endTime = penC.end.time;
zone.locked = false;
// 如果已有确认中枢覆盖了最新结构则不重复显示
int zsTotal = ArraySize(g_zhongshus);
if(zsTotal > 0)
{
ZhongShu last = g_zhongshus[zsTotal-1];
if(last.endTime >= penB.end.time && MathAbs(last.zg - zone.zg) < g_point5 && MathAbs(last.zd - zone.zd) < g_point5)
{
if(BuildFallbackZoneFromFractals(g_fractals, ArraySize(g_fractals), zone))
return(true);
return(false);
}
}
return(true);
}
//+------------------------------------------------------------------+
void DrawDevelopingZone(const ZhongShu &zone)
{
string frame = ObjName("DEVFRAME", 0);
ObjectCreate(0, frame, OBJ_RECTANGLE, 0, zone.startTime, zone.zg, zone.endTime, zone.zd);
ObjectSetInteger(0, frame, OBJPROP_COLOR, clrSlateGray);
ObjectSetInteger(0, frame, OBJPROP_BACK, false);
ObjectSetInteger(0, frame, OBJPROP_FILL, false);
ObjectSetInteger(0, frame, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, frame, OBJPROP_WIDTH, 2);
string upper = ObjName("DEVUPPER", 0);
ObjectCreate(0, upper, OBJ_TREND, 0, zone.startTime, zone.zg, zone.endTime, zone.zg);
ObjectSetInteger(0, upper, OBJPROP_COLOR, clrSlateGray);
ObjectSetInteger(0, upper, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, upper, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, upper, OBJPROP_RAY, false);
string lower = ObjName("DEVLOWER", 0);
ObjectCreate(0, lower, OBJ_TREND, 0, zone.startTime, zone.zd, zone.endTime, zone.zd);
ObjectSetInteger(0, lower, OBJPROP_COLOR, clrSlateGray);
ObjectSetInteger(0, lower, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, lower, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, lower, OBJPROP_RAY, false);
string label = ObjName("DEVLABEL", 0);
datetime midTime = zone.startTime + (zone.endTime - zone.startTime)/2;
ObjectCreate(0, label, OBJ_TEXT, 0, midTime, zone.zg);
ObjectSetString(0, label, OBJPROP_TEXT, zone.a.start.time==0 && zone.b.start.time==0 && zone.c.start.time==0 ? "ZS(fb)" : "ZS(dev)");
ObjectSetInteger(0, label, OBJPROP_COLOR, clrSlateGray);
ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, label, OBJPROP_BACK, false);
}
//+------------------------------------------------------------------+
void ClearDevelopingZoneObjects()
{
int total = ObjectsTotal();
for(int i=total-1; i>=0; --i)
{
string name = ObjectName(0, i);
if(StringFind(name, OBJPREFIX + "DEV") == 0)
ObjectDelete(0, name);
}
}
//+------------------------------------------------------------------+
bool CollectFractalsTF(ENUM_TIMEFRAMES tf, FractalPoint &arr[], int &count, int maxBars)
{
count = 0;
ArrayResize(arr, 0);
int totalBars = iBars(Symbol(), tf);
if(totalBars <= InpFractalDepth*2 + 1)
return(false);
int lastShift = totalBars - InpFractalDepth - 1;
int startShift = MathMax(InpFractalDepth, lastShift - maxBars);
if(startShift > lastShift)
startShift = InpFractalDepth;
for(int shift = lastShift; shift >= startShift; --shift)
{
bool isHigh = IsFractal(tf, shift, true);
bool isLow = IsFractal(tf, shift, false);
if(!isHigh && !isLow)
continue;
FractalPoint fp;
fp.time = iTime(Symbol(), tf, shift);
fp.price = isHigh ? iHigh(Symbol(), tf, shift) : iLow(Symbol(), tf, shift);
fp.isHigh = isHigh;
fp.shift = shift;
AppendFractalLocal(fp, arr, count);
}
return(count > 0);
}
//+------------------------------------------------------------------+
void AppendFractalLocal(FractalPoint &point, FractalPoint &arr[], int &count)
{
if(count > 0 && arr[count-1].isHigh == point.isHigh)
{
if(point.isHigh && point.price > arr[count-1].price)
arr[count-1] = point;
else if(!point.isHigh && point.price < arr[count-1].price)
arr[count-1] = point;
return;
}
ArrayResize(arr, count + 1);
arr[count] = point;
count++;
}
//+------------------------------------------------------------------+
void BuildPensFromFractals(FractalPoint &arr[], int count, Pen &pens[], int &penCount)
{
penCount = 0;
ArrayResize(pens, 0);
if(count < 2)
return;
penCount = count - 1;
ArrayResize(pens, penCount);
for(int i=0; i<penCount; ++i)
{
pens[i].start = arr[i];
pens[i].end = arr[i+1];
pens[i].high = MathMax(arr[i].price, arr[i+1].price);
pens[i].low = MathMin(arr[i].price, arr[i+1].price);
pens[i].confirmed = true;
}
}
//+------------------------------------------------------------------+
bool FindLatestZhongshuLocal(Pen &pens[], int penCount, ZhongShu &zone)
{
bool found = false;
for(int i=0; i<penCount-2; ++i)
{
double zg = MathMin(MathMin(pens[i].high, pens[i+1].high), pens[i+2].high);
double zd = MathMax(MathMax(pens[i].low, pens[i+1].low), pens[i+2].low);
if(zg <= zd)
continue;
zone.a = pens[i];
zone.b = pens[i+1];
zone.c = pens[i+2];
zone.zg = zg;
zone.zd = zd;
zone.startTime = pens[i].start.time;
zone.endTime = pens[i+2].end.time;
zone.locked = true;
found = true;
}
return(found);
}
//+------------------------------------------------------------------+
bool ComputeDevelopingZoneFromData(Pen &pens[], int penCount, FractalPoint &fracs[], int fracCount, ENUM_TIMEFRAMES tf, ZhongShu &zone)
{
if(penCount < 2 || fracCount < 1)
return(false);
Pen penA = pens[penCount-2];
Pen penB = pens[penCount-1];
FractalPoint lastFrac = fracs[fracCount-1];
double nowPrice = iClose(Symbol(), tf, 0);
datetime nowTime = TimeCurrent();
Pen penC;
penC.start = lastFrac;
penC.end.time = nowTime;
penC.end.price = nowPrice;
penC.high = MathMax(lastFrac.price, nowPrice);
penC.low = MathMin(lastFrac.price, nowPrice);
penC.confirmed = false;
double zg = MathMin(MathMin(penA.high, penB.high), penC.high);
double zd = MathMax(MathMax(penA.low, penB.low), penC.low);
if(zg <= zd)
return(false);
zone.a = penA;
zone.b = penB;
zone.c = penC;
zone.zg = zg;
zone.zd = zd;
zone.startTime = penA.start.time;
zone.endTime = penC.end.time;
zone.locked = false;
return(true);
}
//+------------------------------------------------------------------+
bool BuildFallbackZoneFromFractals(FractalPoint &fracs[], int count, ZhongShu &zone)
{
double lastHigh = 0.0, lastLow = 0.0;
datetime highTime = 0, lowTime = 0;
for(int i=count-1; i>=0 && (lastHigh == 0.0 || lastLow == 0.0); --i)
{
if(fracs[i].isHigh && lastHigh == 0.0)
{
lastHigh = fracs[i].price;
highTime = fracs[i].time;
}
else if(!fracs[i].isHigh && lastLow == 0.0)
{
lastLow = fracs[i].price;
lowTime = fracs[i].time;
}
}
if(lastHigh == 0.0 || lastLow == 0.0)
return(false);
double topPrice = lastHigh;
double bottomPrice = lastLow;
datetime topTime = highTime;
datetime bottomTime = lowTime;
if(bottomPrice >= topPrice)
{
topPrice = MathMax(lastHigh, lastLow);
bottomPrice = MathMin(lastHigh, lastLow);
topTime = (topPrice == lastHigh) ? highTime : lowTime;
bottomTime = (bottomPrice == lastLow) ? lowTime : highTime;
if(topPrice - bottomPrice <= 0.0)
return(false);
}
ClearZhongShuStruct(zone);
zone.zg = topPrice;
zone.zd = bottomPrice;
zone.startTime = MathMin(topTime, bottomTime);
zone.endTime = TimeCurrent();
zone.locked = false;
return(true);
}
//+------------------------------------------------------------------+
string TFToString(ENUM_TIMEFRAMES tf)
{
switch(tf)
{
case PERIOD_M1: return("M1");
case PERIOD_M5: return("M5");
case PERIOD_M15: return("M15");
case PERIOD_M30: return("M30");
case PERIOD_H1: return("H1");
case PERIOD_H4: return("H4");
case PERIOD_D1: return("D1");
case PERIOD_W1: return("W1");
case PERIOD_MN1: return("MN1");
}
return("TF");
}
//+------------------------------------------------------------------+
bool ComputeTrendInfo(ENUM_TIMEFRAMES tf, TrendInfo &info)
{
InitTrendInfo(info, tf);
FractalPoint fracs[];
int fracCount = 0;
if(!CollectFractalsTF(tf, fracs, fracCount, 600))
return(false);
if(fracCount >= 2)
{
double highVals[2]={0.0,0.0};
double lowVals[2]={0.0,0.0};
int h=0,l=0;
for(int i=fracCount-1; i>=0 && (h<2 || l<2); --i)
{
if(fracs[i].isHigh && h<2)
highVals[h++] = fracs[i].price;
else if(!fracs[i].isHigh && l<2)
lowVals[l++] = fracs[i].price;
}
if(h==2)
{
if(highVals[0] > highVals[1]) info.highStatus = StringFormat("HH (%.5f > %.5f)", highVals[0], highVals[1]);
else if(highVals[0] < highVals[1]) info.highStatus = StringFormat("LH (%.5f < %.5f)", highVals[0], highVals[1]);
else info.highStatus = StringFormat("Equal (%.5f)", highVals[0]);
}
if(l==2)
{
if(lowVals[0] < lowVals[1]) info.lowStatus = StringFormat("LL (%.5f < %.5f)", lowVals[0], lowVals[1]);
else if(lowVals[0] > lowVals[1]) info.lowStatus = StringFormat("HL (%.5f > %.5f)", lowVals[0], lowVals[1]);
else info.lowStatus = StringFormat("Equal (%.5f)", lowVals[0]);
}
}
if(StringFind(info.highStatus, "HH") == 0 && StringFind(info.lowStatus, "HL") == 0)
info.trendState = "上行";
else if(StringFind(info.highStatus, "LH") == 0 && StringFind(info.lowStatus, "LL") == 0)
info.trendState = "下行";
else
info.trendState = "震荡";
Pen pens[];
int penCount = 0;
BuildPensFromFractals(fracs, fracCount, pens, penCount);
ZhongShu zone;
bool confirmed = FindLatestZhongshuLocal(pens, penCount, zone);
if(confirmed)
{
info.zone = zone;
info.hasZone = true;
info.developing = false;
}
else
{
if(ComputeDevelopingZoneFromData(pens, penCount, fracs, fracCount, tf, zone))
{
info.zone = zone;
info.hasZone = true;
info.developing = true;
}
}
if(info.hasZone)
{
double padPrice = PointsToPrice(InpLiquidityPad);
info.longSL = info.zone.zd - padPrice;
info.shortSL = info.zone.zg + padPrice;
double width = info.zone.zg - info.zone.zd;
double ratio = padPrice > 0.0 ? width / padPrice : 0.0;
if(ratio < 2.0) info.sweepStrength = "紧凑";
else if(ratio < 4.0) info.sweepStrength = "均衡";
else info.sweepStrength = "宽幅";
if(ratio <= 2.0) info.rrHint = "RR好";
else if(ratio <= 4.0) info.rrHint = "RR稳";
else info.rrHint = "RR宽";
}
else if(BuildFallbackZoneFromFractals(fracs, fracCount, info.zone))
{
info.hasZone = true;
info.developing = true;
double padPrice = PointsToPrice(InpLiquidityPad);
info.longSL = info.zone.zd - padPrice;
info.shortSL = info.zone.zg + padPrice;
info.sweepStrength = "预判";
info.rrHint = "待确认";
}
else
{
info.sweepStrength = "无结构";
info.rrHint = "待结构";
}
return(true);
}
//+------------------------------------------------------------------+
void AppendTrendPanelLine(string label, TrendInfo &info, string &panel)
{
panel += label;
panel += " ";
if(info.highStatus == "不足数据" && info.lowStatus == "不足数据")
{
panel += "数据不足\n";
return;
}
string highTag = ShortStatusTag(info.highStatus);
string lowTag = ShortStatusTag(info.lowStatus);
panel += StringFormat("%s/%s %s", highTag, lowTag, info.trendState);
if(info.hasZone)
{
panel += StringFormat(" 区[%s-%s]%s", PriceToStr(info.zone.zd), PriceToStr(info.zone.zg), info.developing ? "*" : "");
panel += StringFormat(" SL%s/%s", PriceToStr(info.longSL), PriceToStr(info.shortSL));
}
else
{
panel += " 区[--]";
}
if(info.rrHint != "")
panel += StringFormat(" %s", info.rrHint);
panel += "\n";
}
//+------------------------------------------------------------------+
void ShareKeyLevelsForM15(TrendInfo &info)
{
if(Period() != InpPrimaryTF)
return;
if(!info.hasZone)
{
GlobalVariableDel(GV_SYNC_ZG);
GlobalVariableDel(GV_SYNC_ZD);
GlobalVariableDel(GV_SYNC_LONGSL);
GlobalVariableDel(GV_SYNC_SHORTSL);
GlobalVariableDel(GV_SYNC_TIME);
GlobalVariableDel(GV_SYNC_DEV);
return;
}
GlobalVariableSet(GV_SYNC_ZG, info.zone.zg);
GlobalVariableSet(GV_SYNC_ZD, info.zone.zd);
GlobalVariableSet(GV_SYNC_LONGSL, info.longSL);
GlobalVariableSet(GV_SYNC_SHORTSL, info.shortSL);
GlobalVariableSet(GV_SYNC_DEV, info.developing ? 1.0 : 0.0);
GlobalVariableSet(GV_SYNC_TIME, (double)TimeCurrent());
}
//+------------------------------------------------------------------+
void EnsureHLine(string name, double price, color clr, ENUM_LINE_STYLE style, int width)
{
if(ObjectFind(0, name) < 0)
{
ObjectCreate(0, name, OBJ_HLINE, 0, TimeCurrent(), price);
}
ObjectSetDouble(0, name, OBJPROP_PRICE, price);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_STYLE, style);
ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
}
//+------------------------------------------------------------------+
void DrawSharedKeyLevels()
{
if(Period() != PERIOD_M15)
return;
if(!GlobalVariableCheck(GV_SYNC_TIME))
return;
double lastTime = GlobalVariableGet(GV_SYNC_TIME);
if(TimeCurrent() - (datetime)lastTime > 3600 * 12)
return;
double zg = GlobalVariableGet(GV_SYNC_ZG);
double zd = GlobalVariableGet(GV_SYNC_ZD);
double longSL = GlobalVariableGet(GV_SYNC_LONGSL);
double shortSL = GlobalVariableGet(GV_SYNC_SHORTSL);
bool dev = GlobalVariableCheck(GV_SYNC_DEV) ? (GlobalVariableGet(GV_SYNC_DEV) > 0.5) : false;
EnsureHLine(OBJ_PREFIX + "SYNC_ZG", zg, clrTomato, STYLE_SOLID, 1);
EnsureHLine(OBJ_PREFIX + "SYNC_ZD", zd, clrLightGreen, STYLE_SOLID, 1);
EnsureHLine(OBJ_PREFIX + "SYNC_LONGSL", longSL, clrDeepSkyBlue, STYLE_DOT, 1);
EnsureHLine(OBJ_PREFIX + "SYNC_SHORTSL", shortSL, clrDeepSkyBlue, STYLE_DOT, 1);
string label = OBJ_PREFIX + "SYNC_LABEL";
if(ObjectFind(0, label) < 0)
ObjectCreate(0, label, OBJ_TEXT, 0, Time[0], zg);
ObjectSetString(0, label, OBJPROP_TEXT, StringFormat("H1 中枢%s | zg %.5f / zd %.5f", dev ? "(dev)" : "", zg, zd));
ObjectSetInteger(0, label, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, label, OBJPROP_BACK, false);
ObjectMove(0, label, 0, Time[0], zg);
}
//+------------------------------------------------------------------+
color DetermineOverlayColor()
{
long fgColor;
if(ChartGetInteger(0, CHART_COLOR_FOREGROUND, 0, fgColor))
return((color)fgColor);
long bgColor;
if(ChartGetInteger(0, CHART_COLOR_BACKGROUND, 0, bgColor))
{
color bg = (color)bgColor;
int r = (int)(bg & 0x0000FF);
int g = (int)((bg & 0x00FF00) >> 8);
int b = (int)((bg & 0xFF0000) >> 16);
double luminance = 0.299 r + 0.587 g + 0.114 * b;
return(luminance > 140.0 ? clrBlack : clrWhite);
}
return(clrWhite);
}
//+------------------------------------------------------------------+
int ColorWithAlpha(color base, int alpha)
{
if(alpha < 0) alpha = 0;
if(alpha > 255) alpha = 255;
return(((alpha & 0xFF) << 24) | (base & 0x00FFFFFF));
}
//+------------------------------------------------------------------+
void ApplyOverlayLabelStyle(const string name, ENUM_BASE_CORNER corner, int xDist, int yDist, int fontSize)
{
color textColor = DetermineOverlayColor();
ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDist);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDist);
ObjectSetInteger(0, name, OBJPROP_COLOR, textColor);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, name, OBJPROP_BACK, false);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);
ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER;
switch(corner)
{
case CORNER_RIGHT_UPPER: anchor = ANCHOR_RIGHT_UPPER; break;
case CORNER_LEFT_LOWER: anchor = ANCHOR_LEFT_LOWER; break;
case CORNER_RIGHT_LOWER: anchor = ANCHOR_RIGHT_LOWER; break;
default: anchor = ANCHOR_LEFT_UPPER; break;
}
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
}
//+------------------------------------------------------------------+
void UpdateTradeOverlayText(string text)
{
string name = OBJ_PREFIX + "TRADE_OVERLAY";
if(ObjectFind(0, name) < 0)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
}
ApplyOverlayLabelStyle(name, CORNER_RIGHT_UPPER, 14, 18, 7);
ObjectSetString(0, name, OBJPROP_TEXT, text);
}
void UpdateStructureOverlayText(string text)
{
string name = OBJ_PREFIX + "STRUCT_OVERLAY";
if(ObjectFind(0, name) < 0)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
}
ApplyOverlayLabelStyle(name, CORNER_LEFT_LOWER, 14, 18, 9);
ObjectSetString(0, name, OBJPROP_TEXT, text);
}
