价格行为分析工具包开发(第 23 部分):货币强弱指标·进阶篇
📘

价格行为分析工具包开发(第 23 部分):货币强弱指标·进阶篇

第 2/2 篇

货币强度面板的初始化与拖拽骨架

想做一套实时货币强度看板,第一步是把 8 个主流币种和它们交叉对的映射关系钉死。下面这段代码用两个静态数组把 USD/EUR/GBP/JPY/CHF/CAD/AUD/NZD 及其对应 4 个观察对(如 USD 对应 USDJPY、EURUSD、GBPUSD、USDCHF)写死,省去运行时查表开销。 OnInit 里先把面板坐标读入 PanelX/PanelY,然后双层循环对 8×4=32 个符号调用 SymbolSelect 强制加载到市场报价窗,否则后续 iClose 取不到数据会直接返回空值。LastUpdate 和 dataCount 归零后立刻跑一次 UpdateStrengths 与 DrawPanel,保证图一开就有内容,不是白板等定时器。 面板不是死贴左上角的。OnChartEvent 捕捉 CHARTEVENT_OBJECT_CLICK 且 sparam=="CS_BG" 时进入 dragging 状态,记录鼠标和面板原点的偏移;MOVE 事件里用 lparam/dparam 减偏移实时重绘。松手时只要再收到一次 OBJECT_CLICK 就把 dragging 置否,逻辑简单但足够防抖。 OnTimer 用 TimeCurrent()-LastUpdate 做节流,不到 Update_Seconds 直接 return,避免同秒多次重算。过了间隔就刷新强度、重画面板,并把 AccountInfoDouble(ACCOUNT_EQUITY) 按 dataCount 推进 times/equities 数组,上限 MAX_POINTS,随后画权益曲线与回撤线。外汇与贵金属波动剧烈,这类面板只做强度参考,实盘仍属高风险。

MQL5 / C++
class="kw">static const class="type">class="kw">string Currencies[class="num">8] = {"USD","EUR","GBP","JPY","CHF","CAD","AUD","NZD"};
class="kw">static const class="type">class="kw">string Pairs[class="num">8][class="num">4] = {
  {"USDJPY","EURUSD","GBPUSD","USDCHF"},
  {"EURUSD","EURJPY","EURGBP","EURCHF"},
  class=class="str">"cmt">// ... .........
};
class="type">class="kw">double Strength[class="num">8][class="num">3]; class=class="str">"cmt">// [currency][timeframe]
class="type">class="kw">datetime LastUpdate;
class="type">int dataCount;
class="type">class="kw">double equities[MAX_POINTS];
class="type">class="kw">datetime times[MAX_POINTS];
class="type">int PanelX, PanelY;
class="type">bool dragging;
class="type">int dragOffsetX, dragOffsetY;
class="type">int OnInit()
{
  PanelX = InitPanelX;
  PanelY = InitPanelY;
  for(class="type">int i=class="num">0; i<class="num">8; i++)
    for(class="type">int j=class="num">0; j<class="num">4; j++)
      SymbolSelect(Pairs[i][j], true);
  LastUpdate = class="num">0;
  dataCount = class="num">0;
  UpdateStrengths();
  DrawPanel();
  LogCurrencyStrengths(class="num">1);
  EventSetTimer(Update_Seconds);
  class="kw">return(INIT_SUCCEEDED);
}
class="type">void OnTimer()
{
  if(TimeCurrent() - LastUpdate < Update_Seconds)
    class="kw">return;
  LastUpdate = TimeCurrent();
  UpdateStrengths();
  DrawPanel();
  LogCurrencyStrengths(class="num">1);
  class="type">class="kw">double eq = AccountInfoDouble(ACCOUNT_EQUITY);
  if(dataCount < MAX_POINTS)
  {
    times[dataCount] = TimeCurrent();
    equities[dataCount] = eq;
    dataCount++;
  }
  DrawEquityLines();
  DrawDrawdownLines();
}
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam)
{
  if(id==CHARTEVENT_OBJECT_CLICK && sparam=="CS_BG")
  {
    dragging = true;
    dragOffsetX = (class="type">int)lparam - PanelX;
    dragOffsetY = (class="type">int)dparam - PanelY;
  }
  else if(id==CHARTEVENT_MOUSE_MOVE && dragging)
  {
    PanelX = (class="type">int)lparam - dragOffsetX;
    PanelY = (class="type">int)dparam - dragOffsetY;
    DrawPanel();
  }
  else if(dragging && id==CHARTEVENT_OBJECT_CLICK)
  {
    dragging = false;
  }
}
class="type">class="kw">double CalculateStrength(class="type">int ci, ENUM_TIMEFRAMES tf, class="type">int lookback)
{
  class="type">class="kw">double sum = class="num">0;
  class="type">int cnt = class="num">0;
  for(class="type">int j=class="num">0; j<class="num">4; j++)
  {
    class="type">class="kw">string sym = Pairs[ci][j];
    if(Bars(sym, tf) < lookback+class="num">1) class="kw">continue;
    class="type">class="kw">double now = iClose(sym, tf, class="num">0);
    class="type">class="kw">double prev = iClose(sym, tf, lookback);

「面板与权益曲线的对象绘制逻辑」

这段脚本把货币强度计算结果和回测权益画到 MT5 图表上。DrawPanel 先调用 RemovePanel 清掉旧物件,再建一个名为 CS_BG 的矩形标签做背景,锚点设在左上角,位置由 PanelX / PanelY 控制,尺寸由 PanelWidth / PanelHeight 决定,注释里预留了表头与彩色条形的扩展位。 RemovePanel 用 ObjectsTotal 倒序遍历,凡是名字以 CS_、Lbl_ 或 Bar_ 开头的对象一律删除,避免重复堆叠导致图表卡顿。 DrawEquityLines 按 dataCount 循环,给每段权益连一条 OBJ_TREND 黄线,宽度设为 2;DrawDrawdownLines 则维护一个 peak 变量,用 MathMax 刷新历史高点,回撤值 = 前一点权益 - peak,再以红色趋势线画出水下部分。外汇与贵金属品种波动剧烈,这类可视化仅辅助判断,实盘仍属高风险。 开 MT5 把这段接进自己的 EA,改 PanelX=10、PanelY=10 就能直接看到左上角浮窗;若权益线不显示,优先查 dataCount 是否大于 1。

MQL5 / C++
if(prev == class="num">0) class="kw">continue;
 class="type">class="kw">double pct = (now - prev) / prev * class="num">100.0;
 if(StringFind(sym, Currencies[ci]) > class="num">0)
    pct = -pct;
  sum += pct;
  cnt++;
  }
  class="kw">return (cnt == class="num">0) ? class="num">0 : sum / cnt;
}
class="type">void DrawPanel()
{
  RemovePanel();
  ObjectCreate(class="num">0, "CS_BG", OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0);
  ObjectSetInteger(class="num">0, "CS_BG", OBJPROP_CORNER, CORNER_LEFT_UPPER);
  ObjectSetInteger(class="num">0, "CS_BG", OBJPROP_XDISTANCE, PanelX);
  ObjectSetInteger(class="num">0, "CS_BG", OBJPROP_YDISTANCE, PanelY);
  ObjectSetInteger(class="num">0, "CS_BG", OBJPROP_XSIZE, PanelWidth);
  ObjectSetInteger(class="num">0, "CS_BG", OBJPROP_YSIZE, PanelHeight);
  class=class="str">"cmt">// Add headers, labels, colored bars for each currency
}
class="type">void RemovePanel()
{
  for(class="type">int i=ObjectsTotal(class="num">0)-class="num">1; i>=class="num">0; i--)
  {
    class="type">class="kw">string name = ObjectName(class="num">0, i);
    if(StringFind(name, "CS_") == class="num">0 || StringFind(name, "Lbl_") == class="num">0 || StringFind(name, "Bar_") == class="num">0)
      ObjectDelete(class="num">0, name);
  }
}
class="type">void DrawEquityLines()
{
  for(class="type">int i=class="num">1; i< dataCount; i++)
  {
    class="type">class="kw">string id = StringFormat("EQ_%d", i);
    ObjectDelete(class="num">0, id);
    ObjectCreate(class="num">0, id, OBJ_TREND, class="num">0,
                  times[i-class="num">1], equities[i-class="num">1],
                  times[i], equities[i]);
    ObjectSetInteger(class="num">0, id, OBJPROP_COLOR, clrYellow);
    ObjectSetInteger(class="num">0, id, OBJPROP_WIDTH, class="num">2);
  }
}
class="type">void DrawDrawdownLines()
{
  class="type">class="kw">double peak = -DBL_MAX;
  for(class="type">int i=class="num">1; i< dataCount; i++)
  {
    peak = MathMax(peak, equities[i-class="num">1]);
    class="type">class="kw">double dd = equities[i-class="num">1] - peak;
    class="type">class="kw">string id = StringFormat("DD_%d", i);
    ObjectDelete(class="num">0, id);
    ObjectCreate(class="num">0, id, OBJ_TREND, class="num">0,
                  times[i-class="num">1], dd,
                  times[i], equities[i] - peak);
    ObjectSetInteger(class="num">0, id, OBJPROP_COLOR, clrRed);

◍ 把货币强弱写进日志的分界阈值

这段函数干的事很直接:遍历八个货币,按当前时间框架的强度值归类成强、弱、中性三组,然后一次性 Print 到终端。 阈值卡在 ±0.3。大于 0.3 的塞进 sStrong,小于 -0.3 的进 sWeak,夹在中间的算 sNeutral。这个 0.3 不是拍脑袋,是前面章节算出的标准化强度量纲里区分趋势倾向和震荡纠缠的常见临界带。 实盘里你可以把 0.3 调到 0.5 看日志,强弱势分组会明显变少,噪音降低但信号滞后;调到 0.2 则分组更碎,早一步捕捉拐点但假信号增多。外汇和贵金属杠杆高,这种归类只反映概率倾向,不能直接当入场指令。 下面这段是原函数,逐行拆一下:void 定义函数,接收时间框架索引 tfIdx;先建三个空字符串装分类结果;for 循环跑 0 到 7 共八个货币;取 Strength 二维数组里对应框架的强度值 v;按 ±0.3 判断归类;最后 Print 拼出三栏。

MQL5 / C++
class="type">void LogCurrencyStrengths(class="type">int tfIdx)
{
  class="type">class="kw">string sStrong="", sWeak="", sNeutral="";
  for(class="type">int i=class="num">0; i<class="num">8; i++)
  {
    class="type">class="kw">double v = Strength[i][tfIdx];
    if(v > class="num">0.3) sStrong += Currencies[i] + " ";
    else if(v < -class="num">0.3) sWeak += Currencies[i] + " ";
    else sNeutral += Currencies[i] + " ";
  }
  Print("Strong: ", sStrong, "| Weak: ", sWeak, "| Neutral: ", sNeutral);
}

回测表里藏着货币强弱差

把 EA 在 MT5 上跑出的百分比变动摊开看,M15 到 H4 三档周期里货币的强弱分化非常直白。美元在 H1 录得 +1.12%、H4 进一步放大到 +2.20%,唯独 M15 微跌 -0.11%,说明短周期噪音不改中长周期主导。 日元在 H1 为 -1.44%、H4 跌到 -2.48%,欧元 H4 也停在 -0.38%,这两个是测试里最弱的一档。英镑、加元、澳元、纽元在 H1/H4 多为 +0.1% 到 +0.76% 的温和正值,算次要强势候选。 H4 上美元 +2.20% 对日元 -2.48%,落差接近 4.7 个百分点,这种宽度倾向指向 USDJPY 做多窗口;同周期欧元 -0.38% 叠美元强势,EURUSD 偏空的概率更高。外汇与贵金属杠杆高,上述只是历史回测的强弱倾向,实盘须自行在 MT5 开图表验证。

「工具矩阵的下一步落点」

货币强弱指标的价值不在「立刻下单」,而在多周期视角下把哪边货币在走强、哪边在走弱先摊开来看。它只是检测货币相对动能的起点,真正进场仍要等价格行为给确认,并用你自己的风控规则卡住仓位。 从 01/10/24 的图表投影仪到 13/05/25 的货币强弱指标,作者已连续放出 22 个工具:09/12/24 的波动性导航工具用布林带+RSI+ATR 做状态分析,14/02/25 的 RSI 背离做价格与指标对照,27/02/25 的侵入检测器专盯价格触四分位。这套矩阵还在迭代,呈现方式与信号融合有明显可改进空间。 外汇与贵金属杠杆高、跳空频繁,这类指标工具只降低盲判概率,不消除亏损可能。开 MT5 把 Currency_Strength.mq5 拖进图表,先跑一周观察 EUR 与 JPY 的跨周期强弱差,再决定要不要接进你的确认系统。

常见问题

检查对象前缀是否冲突,并在 OnInit 里用 OBJPROP_DRAGABLE 设为 true,同时确认图表未锁定。
设一个分界阈值(如强弱差绝对值大于 0.5),用 Print 输出品种对与差值,只在越过阈值时记录,避免刷屏。
可以,小布能读取面板数据并在强弱差越界时推送提醒,你不用一直盯盘也能知道哪组货币对失衡。
在回测表的自定义列导出强弱差序列,统计盈利单集中出现的差值区间,大概率能找到你的策略偏好区间。
用不同子窗口绘制,面板放左上角独立框,权益曲线走主图下方,靠颜色区分,别堆在同一坐标系。