返回
OilSpreadAnalyst
MQL4
EA · 智能交易系统
v1.0
98video
👁 21 浏览
⬇ 14 下载
📅 2026-11-09 00:30:19
更新时间: 2026-11-09 00:43:46
详情
产品概述
OilSpreadAnalyst 是一套用于 MetaTrader 4 的 Brent / WTI 原油相对价值分析仪表盘。它不会自动下单,重点帮助交易者回答三个问题:当前价差是多少、统计上偏贵还是偏便宜、扣除交易成本后是否仍值得关注。
核心功能
- 实时显示 UKOILm(Brent)与 USOILm(WTI)的 BID / ASK、中间价和日涨跌幅。
- 计算 Brent-WTI 中间价差、Brent/WTI 比价、Brent 溢价和双向执行价差。
- 基于最近 N 根 K 线计算均值、标准差、Z-Score、百分位、半衰期、动量和斜率。
- 综合 Z-Score 与动量过滤,给出做多价差、做空价差、观望或等待拐点的读数。
- 同时估算两个方向的回归毛利润、点差与佣金、净收益和保本收敛空间。
- 支持在面板内切换 M15 / H1 / H4,并可手动刷新统计数据。
信号逻辑
默认可信阈值为 Z-Score ±1.5σ。价差显著高于历史均值时,面板偏向“做空价差”,即卖出 Brent、买入 WTI;价差显著低于历史均值时,偏向“做多价差”,即买入 Brent、卖出 WTI。如果极端偏离仍在快速扩散,动量过滤会提示“等待拐点”,避免过早逆势进场。
适用对象
- 平台:MetaTrader 4。
- 默认品种:UKOILm / USOILm,可按经纪商品种名称修改。
- 默认周期:H1;默认统计样本:120 根 K 线。
- 适合观察原油跨品种价差、辅助人工决策和评估交易成本。
使用提醒
本 EA 只提供统计分析与利润测算,不会自动发送订单。历史均值回归不代表未来必然回归;真实交易还需检查滑点、隔夜利息、流动性、保证金和两腿成交时间差。
源代码
MQL4//+------------------------------------------------------------------+
//| OilSpreadAnalyst.mq4 |
//| Brent / WTI spread analysis dashboard |
//+------------------------------------------------------------------+
#property copyright "Oil Spread Analyst"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#property description "Brent/WTI relative-value dashboard with spread, z-score, percentile, half-life and after-cost P&L."
//--- Symbols and statistics
input string BrentSymbol = "UKOILm";
input string WtiSymbol = "USOILm";
input ENUM_TIMEFRAMES AnalysisTimeframe = PERIOD_H1;
input int LookbackBars = 120;
input int MomentumBars = 12;
input double SignalZScore = 1.50;
input double MomentumBlockZ = 0.50;
input bool AvoidWideningSpread = true;
//--- Trade economics
input double TradeLotsPerLeg = 0.10;
input double BrentCommissionPerLot = 0.00;
input double WtiCommissionPerLot = 0.00;
//--- Alerts and refresh
input bool EnableAlerts = true;
input int RefreshSeconds = 1;
//--- Layout
#define PANEL_X 16
#define PANEL_Y 4
#define PANEL_W 980
#define PANEL_H 510
#define OBJECT_PREFIX "OSL_"
const color CLR_PANEL = C'14,19,24';
const color CLR_HEADER = C'20,28,35';
const color CLR_CELL = C'23,31,38';
const color CLR_CELL_ALT = C'27,36,43';
const color CLR_BORDER = C'54,67,76';
const color CLR_TEXT = C'226,232,236';
const color CLR_MUTED = C'145,158,168';
const color CLR_TEAL = C'52,184,166';
const color CLR_TEAL_DARK = C'25,90,84';
const color CLR_ORANGE = C'226,132,64';
const color CLR_RED = C'224,91,88';
const color CLR_GREEN = C'73,196,132';
const color CLR_AMBER = C'228,185,80';
const color CLR_GAUGE = C'34,46,55';
const color CLR_ZERO = C'92,108,119';
struct SpreadStats
{
bool valid;
int samples;
double mean;
double stddev;
double minimum;
double maximum;
double zscore;
double percentile;
double halfLife;
double slope;
double momentum;
datetime lastBarTime;
};
SpreadStats g_stats;
double g_history[];
int g_historyCount = 0;
int g_analysisTf = PERIOD_H1;
int g_signal = 0; // -1 short spread, 0 wait, +1 long spread
int g_lastAlertSignal = 0;
datetime g_lastAlertBar = 0;
datetime g_lastRefresh = 0;
double g_bidBrent = 0.0;
double g_askBrent = 0.0;
double g_bidWti = 0.0;
double g_askWti = 0.0;
double g_midBrent = 0.0;
double g_midWti = 0.0;
double g_midSpread = 0.0;
double g_ratio = 0.0;
double g_premiumPct = 0.0;
double g_valuePerPriceBrent = 0.0;
double g_valuePerPriceWti = 0.0;
double g_spreadUnitValue = 0.0;
double g_signalThreshold = 1.50;
double g_momentumBlock = 0.50;
double g_spreadCostMoney = 0.0;
double g_commissionMoney = 0.0;
double g_totalCostMoney = 0.0;
double g_breakEvenPrice = 0.0;
double g_longGross = 0.0;
double g_shortGross = 0.0;
double g_longNet = 0.0;
double g_shortNet = 0.0;
int g_digitsBrent = 3;
int g_digitsWti = 3;
bool g_quotesReady = false;
bool g_economicsReady = false;
bool g_previousChartForeground = true;
bool g_chartForegroundChanged = false;
//+------------------------------------------------------------------+
//| Expert initialization |
//+------------------------------------------------------------------+
int OnInit()
{
g_analysisTf = (int)AnalysisTimeframe;
g_previousChartForeground = (ChartGetInteger(0, CHART_FOREGROUND) != 0);
g_chartForegroundChanged = false;
if(g_previousChartForeground)
g_chartForegroundChanged = ChartSetInteger(0, CHART_FOREGROUND, false);
ResetStats();
BuildDashboard();
EventSetTimer(MathMax(1, RefreshSeconds));
UpdateDashboard(true);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
DeleteDashboard();
if(g_chartForegroundChanged)
ChartSetInteger(0, CHART_FOREGROUND, g_previousChartForeground);
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Tick handler |
//+------------------------------------------------------------------+
void OnTick()
{
if(RefreshSeconds <= 0 || TimeLocal() - g_lastRefresh >= RefreshSeconds)
UpdateDashboard(false);
}
//+------------------------------------------------------------------+
//| Timer handler |
//+------------------------------------------------------------------+
void OnTimer()
{
UpdateDashboard(false);
}
//+------------------------------------------------------------------+
//| Chart event handler |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id != CHARTEVENT_OBJECT_CLICK)
return;
if(sparam == "OSL_BTN_M15")
{
SetAnalysisTimeframe(PERIOD_M15);
return;
}
if(sparam == "OSL_BTN_H1")
{
SetAnalysisTimeframe(PERIOD_H1);
return;
}
if(sparam == "OSL_BTN_H4")
{
SetAnalysisTimeframe(PERIOD_H4);
return;
}
if(sparam == "OSL_BTN_REFRESH")
{
ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
g_lastRefresh = 0;
UpdateDashboard(true);
return;
}
}
//+------------------------------------------------------------------+
//| Reset calculated state |
//+------------------------------------------------------------------+
void ResetStats()
{
g_stats.valid = false;
g_stats.samples = 0;
g_stats.mean = 0.0;
g_stats.stddev = 0.0;
g_stats.minimum = 0.0;
g_stats.maximum = 0.0;
g_stats.zscore = 0.0;
g_stats.percentile = 0.0;
g_stats.halfLife = 0.0;
g_stats.slope = 0.0;
g_stats.momentum = 0.0;
g_stats.lastBarTime = 0;
ArrayResize(g_history, 0);
g_historyCount = 0;
}
//+------------------------------------------------------------------+
//| Change analysis timeframe |
//+------------------------------------------------------------------+
void SetAnalysisTimeframe(const int timeframe)
{
if(g_analysisTf == timeframe)
{
ObjectSetInteger(0, "OSL_BTN_M15", OBJPROP_STATE, false);
ObjectSetInteger(0, "OSL_BTN_H1", OBJPROP_STATE, false);
ObjectSetInteger(0, "OSL_BTN_H4", OBJPROP_STATE, false);
UpdateTimeframeButtons();
ChartRedraw();
return;
}
g_analysisTf = timeframe;
g_lastAlertSignal = 0;
g_lastAlertBar = 0;
ResetStats();
UpdateTimeframeButtons();
g_lastRefresh = 0;
UpdateDashboard(true);
}
//+------------------------------------------------------------------+
//| Main dashboard refresh |
//+------------------------------------------------------------------+
void UpdateDashboard(const bool force)
{
if(!force && RefreshSeconds > 0 && TimeLocal() - g_lastRefresh < RefreshSeconds)
return;
g_lastRefresh = TimeLocal();
g_signalThreshold = MathMax(0.25, SignalZScore);
g_momentumBlock = MathMax(0.0, MomentumBlockZ);
g_quotesReady = ReadQuotes();
if(!g_quotesReady)
{
UpdateUnavailableState();
ChartRedraw();
return;
}
g_midBrent = (g_bidBrent + g_askBrent) * 0.5;
g_midWti = (g_bidWti + g_askWti) * 0.5;
g_midSpread = g_midBrent - g_midWti;
g_ratio = (g_midWti > 0.0 ? g_midBrent / g_midWti : 0.0);
g_premiumPct = (g_ratio - 1.0) * 100.0;
CalculateStatistics();
CalculateEconomics();
DetermineSignal();
UpdateHeader();
UpdateQuoteBlocks();
UpdateSpreadBlock();
UpdateStatisticsBlock();
UpdateGauge();
UpdateSignalBlock();
UpdateTradeBlocks();
UpdateFooterStatus();
UpdateTimeframeButtons();
CheckSignalAlert();
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Read live quotes and symbol properties |
//+------------------------------------------------------------------+
bool ReadQuotes()
{
g_bidBrent = MarketInfo(BrentSymbol, MODE_BID);
g_askBrent = MarketInfo(BrentSymbol, MODE_ASK);
g_bidWti = MarketInfo(WtiSymbol, MODE_BID);
g_askWti = MarketInfo(WtiSymbol, MODE_ASK);
if(g_bidBrent <= 0.0 || g_askBrent <= 0.0 ||
g_bidWti <= 0.0 || g_askWti <= 0.0)
return false;
if(g_askBrent < g_bidBrent || g_askWti < g_bidWti)
return false;
g_digitsBrent = (int)MarketInfo(BrentSymbol, MODE_DIGITS);
g_digitsWti = (int)MarketInfo(WtiSymbol, MODE_DIGITS);
double tickSizeBrent = MarketInfo(BrentSymbol, MODE_TICKSIZE);
double tickSizeWti = MarketInfo(WtiSymbol, MODE_TICKSIZE);
double pointBrent = MarketInfo(BrentSymbol, MODE_POINT);
double pointWti = MarketInfo(WtiSymbol, MODE_POINT);
double tickValueBrent = MarketInfo(BrentSymbol, MODE_TICKVALUE);
double tickValueWti = MarketInfo(WtiSymbol, MODE_TICKVALUE);
if(tickSizeBrent <= 0.0)
tickSizeBrent = pointBrent;
if(tickSizeWti <= 0.0)
tickSizeWti = pointWti;
g_valuePerPriceBrent = (tickSizeBrent > 0.0 ? tickValueBrent / tickSizeBrent : 0.0);
g_valuePerPriceWti = (tickSizeWti > 0.0 ? tickValueWti / tickSizeWti : 0.0);
g_spreadUnitValue = (g_valuePerPriceBrent + g_valuePerPriceWti) * 0.5;
g_economicsReady = (g_valuePerPriceBrent > 0.0 && g_valuePerPriceWti > 0.0);
return true;
}
//+------------------------------------------------------------------+
//| Calculate rolling spread statistics |
//+------------------------------------------------------------------+
bool CalculateStatistics()
{
ResetStats();
int barsBrent = iBars(BrentSymbol, g_analysisTf);
int barsWti = iBars(WtiSymbol, g_analysisTf);
int requested = MathMax(20, MathMin(LookbackBars, 1000));
int available = MathMin(barsBrent - 2, barsWti - 2);
if(available < 20)
return false;
int sampleCount = MathMin(requested, available);
double values[];
ArrayResize(values, sampleCount);
ArrayInitialize(values, 0.0);
int n = 0;
int periodSeconds = PeriodSeconds(g_analysisTf);
// Oldest completed bar first, newest completed bar last.
for(int k = sampleCount; k >= 1; k--)
{
datetime brentTime = iTime(BrentSymbol, g_analysisTf, k);
if(brentTime <= 0)
continue;
int wtiShift = iBarShift(WtiSymbol, g_analysisTf, brentTime, false);
if(wtiShift < 0)
continue;
datetime wtiTime = iTime(WtiSymbol, g_analysisTf, wtiShift);
if(wtiTime <= 0 || MathAbs((long)brentTime - (long)wtiTime) > periodSeconds)
continue;
double brentClose = iClose(BrentSymbol, g_analysisTf, k);
double wtiClose = iClose(WtiSymbol, g_analysisTf, wtiShift);
if(brentClose <= 0.0 || wtiClose <= 0.0)
continue;
values[n] = brentClose - wtiClose;
n++;
}
if(n < 20)
return false;
ArrayResize(values, n);
ArrayResize(g_history, n);
g_historyCount = n;
double sum = 0.0;
double minimum = values[0];
double maximum = values[0];
for(int i = 0; i < n; i++)
{
g_history[i] = values[i];
sum += values[i];
if(values[i] < minimum)
minimum = values[i];
if(values[i] > maximum)
maximum = values[i];
}
double mean = sum / n;
double squared = 0.0;
for(int j = 0; j < n; j++)
squared += MathPow(values[j] - mean, 2.0);
double stddev = (n > 1 ? MathSqrt(squared / (n - 1)) : 0.0);
double zscore = (stddev > 0.0 ? (g_midSpread - mean) / stddev : 0.0);
int belowOrEqual = 0;
for(int p = 0; p < n; p++)
{
if(values[p] <= g_midSpread)
belowOrEqual++;
}
double percentile = 100.0 * (double)belowOrEqual / (double)n;
int momentumBars = MathMax(2, MathMin(MomentumBars, n - 1));
double momentum = (stddev > 0.0 ?
(g_midSpread - values[n - 1 - momentumBars]) / stddev : 0.0);
int slopeBars = MathMin(MathMax(MomentumBars, 5), n);
double slope = CalculateSlope(values, n - slopeBars, slopeBars);
double halfLife = CalculateHalfLife(values, n);
g_stats.valid = true;
g_stats.samples = n;
g_stats.mean = mean;
g_stats.stddev = stddev;
g_stats.minimum = minimum;
g_stats.maximum = maximum;
g_stats.zscore = zscore;
g_stats.percentile = percentile;
g_stats.halfLife = halfLife;
g_stats.slope = slope;
g_stats.momentum = momentum;
g_stats.lastBarTime = iTime(BrentSymbol, g_analysisTf, 1);
return true;
}
//+------------------------------------------------------------------+
//| Linear regression slope per bar |
//+------------------------------------------------------------------+
double CalculateSlope(const double &values[], const int start, const int count)
{
if(count < 2 || start < 0 || start + count > ArraySize(values))
return 0.0;
double sumX = 0.0;
double sumY = 0.0;
double sumXY = 0.0;
double sumXX = 0.0;
for(int i = 0; i < count; i++)
{
double x = (double)i;
double y = values[start + i];
sumX += x;
sumY += y;
sumXY += x * y;
sumXX += x * x;
}
double denominator = count * sumXX - sumX * sumX;
if(MathAbs(denominator) < 0.0000001)
return 0.0;
return (count * sumXY - sumX * sumY) / denominator;
}
//+------------------------------------------------------------------+
//| AR(1) half-life in bars |
//+------------------------------------------------------------------+
double CalculateHalfLife(const double &values[], const int count)
{
if(count < 10)
return 0.0;
double sumX = 0.0;
double sumY = 0.0;
for(int i = 1; i < count; i++)
{
sumX += values[i - 1];
sumY += values[i] - values[i - 1];
}
int observations = count - 1;
double meanX = sumX / observations;
double meanY = sumY / observations;
double covariance = 0.0;
double variance = 0.0;
for(int j = 1; j < count; j++)
{
double x = values[j - 1] - meanX;
double y = (values[j] - values[j - 1]) - meanY;
covariance += x * y;
variance += x * x;
}
if(variance <= 0.0)
return 0.0;
double beta = covariance / variance;
if(beta >= 0.0 || beta <= -0.999999)
return 0.0;
double halfLife = -MathLog(2.0) / MathLog(1.0 + beta);
if(halfLife <= 0.0)
return 0.0;
return halfLife;
}
//+------------------------------------------------------------------+
//| Calculate after-cost economics for both spread directions |
//+------------------------------------------------------------------+
void CalculateEconomics()
{
double spreadBrent = MathMax(0.0, g_askBrent - g_bidBrent);
double spreadWti = MathMax(0.0, g_askWti - g_bidWti);
double lots = MathMax(0.0, TradeLotsPerLeg);
g_spreadCostMoney =
(spreadBrent * g_valuePerPriceBrent +
spreadWti * g_valuePerPriceWti) * lots;
g_commissionMoney =
(MathMax(0.0, BrentCommissionPerLot) +
MathMax(0.0, WtiCommissionPerLot)) * lots;
g_totalCostMoney = g_spreadCostMoney + g_commissionMoney;
g_longGross = (g_stats.valid ?
(g_stats.mean - g_midSpread) * g_spreadUnitValue * lots : 0.0);
g_shortGross = (g_stats.valid ?
(g_midSpread - g_stats.mean) * g_spreadUnitValue * lots : 0.0);
g_longNet = g_longGross - g_totalCostMoney;
g_shortNet = g_shortGross - g_totalCostMoney;
double unitValueAtLots = g_spreadUnitValue * lots;
g_breakEvenPrice = (unitValueAtLots > 0.0 ?
g_totalCostMoney / unitValueAtLots : 0.0);
}
//+------------------------------------------------------------------+
//| Select statistical signal with a widening-spread filter |
//+------------------------------------------------------------------+
void DetermineSignal()
{
g_signal = 0;
if(!g_stats.valid)
return;
int rawSignal = 0;
if(g_stats.zscore >= g_signalThreshold)
rawSignal = -1;
else if(g_stats.zscore <= -g_signalThreshold)
rawSignal = 1;
if(rawSignal == 0)
return;
bool blockedByMomentum = false;
if(AvoidWideningSpread)
{
if(rawSignal == -1 && g_stats.momentum > g_momentumBlock)
blockedByMomentum = true;
if(rawSignal == 1 && g_stats.momentum < -g_momentumBlock)
blockedByMomentum = true;
}
if(!blockedByMomentum)
g_signal = rawSignal;
}
//+------------------------------------------------------------------+
//| Send a signal-transition alert on a new bar |
//+------------------------------------------------------------------+
void CheckSignalAlert()
{
if(!EnableAlerts || !g_stats.valid)
return;
datetime currentBar = iTime(BrentSymbol, g_analysisTf, 0);
if(currentBar <= 0 || currentBar == g_lastAlertBar)
return;
g_lastAlertBar = currentBar;
if(g_signal == 0)
{
g_lastAlertSignal = 0;
return;
}
if(g_signal == g_lastAlertSignal)
return;
string action = (g_signal > 0 ? "做多价差" : "做空价差");
string message = StringFormat(
"%s %s/%s | %s | 价差 %.3f | z %.2f | 分位 %.1f%% | 预计净利 %s %.2f",
BrentSymbol, WtiSymbol, TimeframeName(g_analysisTf), action,
g_midSpread, g_stats.zscore, g_stats.percentile,
AccountCurrency(), (g_signal > 0 ? g_longNet : g_shortNet));
Alert(message);
g_lastAlertSignal = g_signal;
}
//+------------------------------------------------------------------+
//| Build all chart objects |
//+------------------------------------------------------------------+
void BuildDashboard()
{
DeleteDashboard();
CreateRect("OSL_BG", 0, 0, PANEL_W, PANEL_H, CLR_PANEL, CLR_BORDER);
CreateRect("OSL_HEADER", 0, 0, PANEL_W, 50, CLR_HEADER, CLR_BORDER);
CreateRect("OSL_ACCENT", 0, 0, 5, 50, CLR_TEAL, CLR_TEAL);
CreateLabel("OSL_TITLE", 22, 10, "OIL SPREAD LAB", 16, CLR_TEXT, "Segoe UI Semibold");
CreateLabel("OSL_SUBTITLE", 22, 30, "Brent / WTI 相对价值分析台", 9, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_HEADER_SYMBOLS", 560, 11, BrentSymbol + " / " + WtiSymbol, 9, CLR_TEXT, "Consolas");
CreateLabel("OSL_HEADER_TIME", 560, 31, "等待行情", 8, CLR_MUTED, "Consolas");
CreateRect("OSL_SYM1_BG", 16, 58, 468, 74, CLR_CELL, CLR_BORDER);
CreateRect("OSL_SYM2_BG", 496, 58, 468, 74, CLR_CELL, CLR_BORDER);
CreateLabel("OSL_SYM1_NAME", 31, 65, BrentSymbol + " · BRENT", 9, CLR_TEAL, "Segoe UI Semibold");
CreateLabel("OSL_SYM1_PRICE", 31, 82, "--", 22, CLR_TEXT, "Consolas");
CreateLabel("OSL_SYM1_QUOTE", 31, 116, "BID -- / ASK --", 8, CLR_MUTED, "Consolas");
CreateLabel("OSL_SYM1_CHANGE", 468, 65, "--", 8, CLR_MUTED, "Consolas", ANCHOR_RIGHT_UPPER);
CreateLabel("OSL_SYM2_NAME", 511, 65, WtiSymbol + " · WTI", 9, CLR_ORANGE, "Segoe UI Semibold");
CreateLabel("OSL_SYM2_PRICE", 511, 82, "--", 22, CLR_TEXT, "Consolas");
CreateLabel("OSL_SYM2_QUOTE", 511, 116, "BID -- / ASK --", 8, CLR_MUTED, "Consolas");
CreateLabel("OSL_SYM2_CHANGE", 948, 65, "--", 8, CLR_MUTED, "Consolas", ANCHOR_RIGHT_UPPER);
CreateRect("OSL_SPREAD_BG", 16, 142, 320, 168, CLR_CELL_ALT, CLR_BORDER);
CreateLabel("OSL_SPREAD_CAPTION", 31, 150, "MID SPREAD · BRENT - WTI", 8, CLR_MUTED, "Segoe UI Semibold");
CreateLabel("OSL_SPREAD_VALUE", 31, 169, "--", 28, CLR_TEXT, "Consolas");
CreateLabel("OSL_SPREAD_UNIT", 31, 207, "USD / bbl", 8, CLR_MUTED, "Segoe UI");
CreateLabel("OSL_RATIO_CAPTION", 185, 151, "BRENT / WTI 比价", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_RATIO_VALUE", 185, 170, "--", 18, CLR_TEXT, "Consolas");
CreateLabel("OSL_PREMIUM_CAPTION", 260, 151, "BRENT 溢价", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_PREMIUM_VALUE", 260, 170, "--", 18, CLR_TEXT, "Consolas");
CreateLabel("OSL_EXEC_SPREADS", 31, 235, "执行价差 多头 -- / 空头 --", 8, CLR_MUTED, "Consolas");
CreateLabel("OSL_SPREAD_NOTE", 31, 274,
"多头 = 买 Brent / 卖 WTI 空头 = 卖 Brent / 买 WTI",
7, CLR_MUTED, "Microsoft YaHei");
CreateRect("OSL_STATS_BG", 344, 142, 620, 168, CLR_CELL, CLR_BORDER);
CreateStatBlock("OSL_STAT_MEAN", 360, 150, "均值", "MEAN");
CreateStatBlock("OSL_STAT_SD", 566, 150, "标准差", "STD DEV");
CreateStatBlock("OSL_STAT_Z", 772, 150, "Z-SCORE", "偏离");
CreateStatBlock("OSL_STAT_PCT", 360, 218, "百分位", "PERCENTILE");
CreateStatBlock("OSL_STAT_HL", 566, 218, "半衰期", "HALF-LIFE");
CreateStatBlock("OSL_STAT_SLOPE", 772, 218, "动量", "SLOPE");
CreateRect("OSL_SIGNAL_BG", 16, 318, 480, 144, CLR_CELL, CLR_BORDER);
CreateLabel("OSL_SIGNAL_CAPTION", 31, 326, "策略信号", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_SIGNAL_ACTION", 31, 342, "等待数据", 15, CLR_AMBER, "Microsoft YaHei");
CreateLabel("OSL_SIGNAL_REASON", 31, 367, "--", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_SIGNAL_STRENGTH", 480, 326, "--", 8, CLR_MUTED, "Consolas", ANCHOR_RIGHT_UPPER);
CreateLabel("OSL_GAUGE_CAPTION", 31, 389, "Z-SCORE 区间 · 统计偏离强度", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_GAUGE_NEG2", 109, 401, "-2σ", 7, CLR_MUTED, "Segoe UI");
CreateLabel("OSL_GAUGE_NEG1", 179, 401, "-1σ", 7, CLR_MUTED, "Segoe UI");
CreateLabel("OSL_GAUGE_ZERO", 249, 401, "0", 7, CLR_MUTED, "Segoe UI");
CreateLabel("OSL_GAUGE_POS1", 317, 401, "+1σ", 7, CLR_MUTED, "Segoe UI");
CreateLabel("OSL_GAUGE_POS2", 387, 401, "+2σ", 7, CLR_MUTED, "Segoe UI");
CreateRect("OSL_GAUGE_TRACK", 48, 419, 416, 10, CLR_GAUGE, CLR_GAUGE);
CreateRect("OSL_GAUGE_LONG_ZONE", 48, 419, 104, 10, CLR_TEAL_DARK, CLR_TEAL_DARK);
CreateRect("OSL_GAUGE_SHORT_ZONE", 360, 419, 104, 10, C'84,50,35', C'84,50,35');
CreateRect("OSL_GAUGE_MID", 255, 417, 2, 14, CLR_ZERO, CLR_ZERO);
CreateRect("OSL_GAUGE_FILL", 255, 421, 2, 6, CLR_TEAL, CLR_TEAL);
CreateRect("OSL_GAUGE_MARKER", 253, 416, 6, 16, CLR_TEXT, CLR_TEXT);
CreateLabel("OSL_GAUGE_LEFT", 48, 434, "做多价差区", 7, CLR_TEAL, "Microsoft YaHei");
CreateLabel("OSL_GAUGE_RIGHT", 464, 434, "做空价差区", 7, CLR_ORANGE, "Microsoft YaHei", ANCHOR_RIGHT_UPPER);
CreateRect("OSL_TRADE_BG", 504, 318, 460, 144, CLR_CELL, CLR_BORDER);
CreateRect("OSL_TRADE_DIVIDER", 734, 326, 1, 112, CLR_BORDER, CLR_BORDER);
CreateLabel("OSL_LONG_TITLE", 519, 326, "做多价差", 10, CLR_TEAL, "Microsoft YaHei");
CreateLabel("OSL_LONG_SUB", 519, 339, "买 Brent / 卖 WTI", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_LONG_NET", 519, 352, "净利 @ 均值 --", 16, CLR_TEXT, "Consolas");
CreateLabel("OSL_LONG_GROSS", 519, 379, "回归毛利 --", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_LONG_COST", 519, 396, "点差 + 佣金 --", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_LONG_BE", 519, 413, "保本收敛 --", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_SHORT_TITLE", 749, 326, "做空价差", 10, CLR_ORANGE, "Microsoft YaHei");
CreateLabel("OSL_SHORT_SUB", 749, 339, "卖 Brent / 买 WTI", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_SHORT_NET", 749, 352, "净利 @ 均值 --", 16, CLR_TEXT, "Consolas");
CreateLabel("OSL_SHORT_GROSS", 749, 379, "回归毛利 --", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_SHORT_COST", 749, 396, "点差 + 佣金 --", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_SHORT_BE", 749, 413, "保本收敛 --", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_TRADE_LOTS", 519, 441, "测算手数:每腿 -- 手", 8, CLR_MUTED, "Microsoft YaHei");
CreateRect("OSL_FOOTER", 16, 470, 948, 36, CLR_HEADER, CLR_BORDER);
CreateLabel("OSL_FOOTER_STATUS", 31, 475, "正在读取历史行情...", 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel("OSL_FOOTER_ASSUMPTION", 31, 492,
"净利假设:价差回归均值、两腿等幅收敛;不含隔夜利息与滑点。", 7, CLR_MUTED,
"Microsoft YaHei");
CreateButton("OSL_BTN_M15", 680, 476, 50, 24, "M15");
CreateButton("OSL_BTN_H1", 737, 476, 50, 24, "H1");
CreateButton("OSL_BTN_H4", 794, 476, 50, 24, "H4");
CreateButton("OSL_BTN_REFRESH", 851, 476, 62, 24, "刷新");
}
//+------------------------------------------------------------------+
//| Create one statistic block |
//+------------------------------------------------------------------+
void CreateStatBlock(const string name, const int x, const int y,
const string cn, const string en)
{
CreateLabel(name + "_CAPTION", x, y, cn + " · " + en, 8, CLR_MUTED, "Microsoft YaHei");
CreateLabel(name + "_VALUE", x, y + 16, "--", 14, CLR_TEXT, "Consolas");
CreateLabel(name + "_SUB", x, y + 43, "--", 8, CLR_MUTED, "Consolas");
}
//+------------------------------------------------------------------+
//| Update header |
//+------------------------------------------------------------------+
void UpdateHeader()
{
SetLabelText("OSL_HEADER_SYMBOLS", BrentSymbol + " / " + WtiSymbol);
SetLabelText("OSL_HEADER_TIME", "服务器 " + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS));
SetLabelText("OSL_SYM1_NAME", BrentSymbol + " · BRENT");
SetLabelText("OSL_SYM2_NAME", WtiSymbol + " · WTI");
}
//+------------------------------------------------------------------+
//| Update live quote blocks |
//+------------------------------------------------------------------+
void UpdateQuoteBlocks()
{
SetLabelText("OSL_SYM1_PRICE", FormatPrice(g_midBrent, g_digitsBrent));
SetLabelText("OSL_SYM2_PRICE", FormatPrice(g_midWti, g_digitsWti));
SetLabelText("OSL_SYM1_QUOTE",
"BID " + FormatPrice(g_bidBrent, g_digitsBrent) +
" / ASK " + FormatPrice(g_askBrent, g_digitsBrent));
SetLabelText("OSL_SYM2_QUOTE",
"BID " + FormatPrice(g_bidWti, g_digitsWti) +
" / ASK " + FormatPrice(g_askWti, g_digitsWti));
UpdateDailyChange("OSL_SYM1_CHANGE", BrentSymbol, g_midBrent, g_digitsBrent);
UpdateDailyChange("OSL_SYM2_CHANGE", WtiSymbol, g_midWti, g_digitsWti);
}
//+------------------------------------------------------------------+
//| Update daily change labels |
//+------------------------------------------------------------------+
void UpdateDailyChange(const string name, const string symbol,
const double currentMid, const int digits)
{
double previousClose = iClose(symbol, PERIOD_D1, 1);
if(previousClose <= 0.0 || currentMid <= 0.0)
{
SetLabelText(name, "日变动 --");
SetLabelColor(name, CLR_MUTED);
return;
}
double change = currentMid - previousClose;
double percent = change / previousClose * 100.0;
string sign = (change > 0.0 ? "+" : "");
string text = "日变动 " + sign + FormatPrice(change, digits) +
" (" + sign + DoubleToString(percent, 2) + "%)";
SetLabelText(name, text);
SetLabelColor(name, (change > 0.0 ? CLR_GREEN : (change < 0.0 ? CLR_RED : CLR_MUTED)));
}
//+------------------------------------------------------------------+
//| Update spread and ratio block |
//+------------------------------------------------------------------+
void UpdateSpreadBlock()
{
SetLabelText("OSL_SPREAD_VALUE", DoubleToString(g_midSpread, 3));
SetLabelText("OSL_RATIO_VALUE", DoubleToString(g_ratio, 4));
string premiumSign = (g_premiumPct > 0.0 ? "+" : "");
SetLabelText("OSL_PREMIUM_VALUE", premiumSign + DoubleToString(g_premiumPct, 2) + "%");
SetLabelColor("OSL_PREMIUM_VALUE", (g_premiumPct >= 0.0 ? CLR_ORANGE : CLR_TEAL));
double longExecutable = g_askBrent - g_bidWti;
double shortExecutable = g_bidBrent - g_askWti;
SetLabelText("OSL_EXEC_SPREADS",
"执行价差 多头 " + DoubleToString(longExecutable, 3) +
" / 空头 " + DoubleToString(shortExecutable, 3));
}
//+------------------------------------------------------------------+
//| Update statistics block |
//+------------------------------------------------------------------+
void UpdateStatisticsBlock()
{
if(!g_stats.valid)
{
SetLabelText("OSL_STAT_MEAN_VALUE", "--");
SetLabelText("OSL_STAT_SD_VALUE", "--");
SetLabelText("OSL_STAT_Z_VALUE", "--");
SetLabelText("OSL_STAT_PCT_VALUE", "--");
SetLabelText("OSL_STAT_HL_VALUE", "--");
SetLabelText("OSL_STAT_SLOPE_VALUE", "--");
return;
}
SetLabelText("OSL_STAT_MEAN_VALUE", DoubleToString(g_stats.mean, 3));
SetLabelText("OSL_STAT_MEAN_SUB",
"范围 " + DoubleToString(g_stats.minimum, 2) +
" ~ " + DoubleToString(g_stats.maximum, 2));
SetLabelText("OSL_STAT_SD_VALUE", DoubleToString(g_stats.stddev, 3));
SetLabelText("OSL_STAT_SD_SUB", "样本 " + IntegerToString(g_stats.samples));
string zSign = (g_stats.zscore > 0.0 ? "+" : "");
SetLabelText("OSL_STAT_Z_VALUE", zSign + DoubleToString(g_stats.zscore, 2));
SetLabelColor("OSL_STAT_Z_VALUE",
(g_stats.zscore >= g_signalThreshold ? CLR_ORANGE :
(g_stats.zscore <= -g_signalThreshold ? CLR_TEAL : CLR_TEXT)));
SetLabelText("OSL_STAT_Z_SUB", "阈值 ±" + DoubleToString(g_signalThreshold, 2) + "σ");
SetLabelText("OSL_STAT_PCT_VALUE", DoubleToString(g_stats.percentile, 1) + "%");
int rank = (int)MathRound(g_stats.percentile);
SetLabelText("OSL_STAT_PCT_SUB", "历史排名 " + IntegerToString(rank) + "/100");
if(g_stats.halfLife > 0.0)
SetLabelText("OSL_STAT_HL_VALUE", DoubleToString(g_stats.halfLife, 1));
else
SetLabelText("OSL_STAT_HL_VALUE", "N/A");
SetLabelText("OSL_STAT_HL_SUB", "根 K 线");
string slopeSign = (g_stats.slope > 0.0 ? "+" : "");
SetLabelText("OSL_STAT_SLOPE_VALUE", slopeSign + DoubleToString(g_stats.slope, 4));
SetLabelColor("OSL_STAT_SLOPE_VALUE",
(g_stats.slope > 0.0 ? CLR_ORANGE :
(g_stats.slope < 0.0 ? CLR_TEAL : CLR_TEXT)));
string momentumSign = (g_stats.momentum > 0.0 ? "+" : "");
SetLabelText("OSL_STAT_SLOPE_SUB",
"动量 " + momentumSign + DoubleToString(g_stats.momentum, 2) + "σ");
}
//+------------------------------------------------------------------+
//| Update z-score gauge |
//+------------------------------------------------------------------+
void UpdateGauge()
{
double z = (g_stats.valid ? g_stats.zscore : 0.0);
double clamped = MathMax(-3.0, MathMin(3.0, z));
int trackX = 48;
int centerX = trackX + 208;
int markerX = trackX + (int)MathRound((clamped + 3.0) / 6.0 * 416.0);
markerX = MathMax(trackX + 1, MathMin(trackX + 415, markerX));
color activeColor = CLR_ZERO;
if(z >= g_signalThreshold)
activeColor = CLR_ORANGE;
else if(z <= -g_signalThreshold)
activeColor = CLR_TEAL;
int fillX = MathMin(centerX, markerX);
int fillWidth = MathMax(2, MathAbs(markerX - centerX));
SetRectGeometry("OSL_GAUGE_FILL", fillX, 421, fillWidth, 6);
SetRectColor("OSL_GAUGE_FILL", activeColor, activeColor);
SetRectGeometry("OSL_GAUGE_MARKER", markerX - 3, 416, 6, 16);
SetRectColor("OSL_GAUGE_MARKER", activeColor, activeColor);
}
//+------------------------------------------------------------------+
//| Update signal block |
//+------------------------------------------------------------------+
void UpdateSignalBlock()
{
if(!g_stats.valid)
{
SetLabelText("OSL_SIGNAL_ACTION", "等待历史数据");
SetLabelColor("OSL_SIGNAL_ACTION", CLR_AMBER);
SetLabelText("OSL_SIGNAL_REASON", "双品种历史尚未对齐,请检查品种名称与图表历史。");
SetLabelText("OSL_SIGNAL_STRENGTH", "--");
return;
}
string zSign = (g_stats.zscore > 0.0 ? "+" : "");
string reason = "z " + zSign + DoubleToString(g_stats.zscore, 2) +
"σ · " + DoubleToString(g_stats.percentile, 1) + "%分位";
if(g_stats.halfLife > 0.0)
reason += " · 半衰期 " + DoubleToString(g_stats.halfLife, 1) + "根";
else
reason += " · 均值回归证据弱";
string action = "观望 · 未达到统计极值";
color actionColor = CLR_AMBER;
if(g_signal > 0)
{
action = "做多价差 · 买 Brent / 卖 WTI";
actionColor = CLR_TEAL;
}
else if(g_signal < 0)
{
action = "做空价差 · 卖 Brent / 买 WTI";
actionColor = CLR_ORANGE;
}
else if(AvoidWideningSpread &&
((g_stats.zscore >= g_signalThreshold && g_stats.momentum > g_momentumBlock) ||
(g_stats.zscore <= -g_signalThreshold && g_stats.momentum < -g_momentumBlock)))
{
action = "等待拐点 · 价差仍在扩散";
actionColor = CLR_AMBER;
reason += " · 动量过滤已触发";
}
double strength = MathMin(100.0, MathAbs(g_stats.zscore) / 3.0 * 100.0);
string strengthText = "偏离强度 " + DoubleToString(strength, 0) + "/100";
SetLabelText("OSL_SIGNAL_ACTION", action);
SetLabelColor("OSL_SIGNAL_ACTION", actionColor);
SetLabelText("OSL_SIGNAL_REASON", reason);
SetLabelText("OSL_SIGNAL_STRENGTH", strengthText);
}
//+------------------------------------------------------------------+
//| Update both strategy economics blocks |
//+------------------------------------------------------------------+
void UpdateTradeBlocks()
{
if(!g_stats.valid || !g_economicsReady)
{
SetLabelText("OSL_LONG_NET", "净利 @ 均值 --");
SetLabelText("OSL_SHORT_NET", "净利 @ 均值 --");
SetLabelText("OSL_LONG_GROSS", "回归毛利 --");
SetLabelText("OSL_SHORT_GROSS", "回归毛利 --");
SetLabelText("OSL_LONG_COST", "点差 + 佣金 --");
SetLabelText("OSL_SHORT_COST", "点差 + 佣金 --");
SetLabelText("OSL_LONG_BE", "保本收敛 --");
SetLabelText("OSL_SHORT_BE", "保本收敛 --");
SetLabelText("OSL_TRADE_LOTS", "测算手数:每腿 -- 手");
return;
}
string currency = AccountCurrency();
SetLabelText("OSL_LONG_NET", "净利 @ 均值 " + SignedMoney(g_longNet, currency));
SetLabelColor("OSL_LONG_NET", (g_longNet >= 0.0 ? CLR_GREEN : CLR_RED));
SetLabelText("OSL_SHORT_NET", "净利 @ 均值 " + SignedMoney(g_shortNet, currency));
SetLabelColor("OSL_SHORT_NET", (g_shortNet >= 0.0 ? CLR_GREEN : CLR_RED));
SetLabelText("OSL_LONG_GROSS", "回归毛利 " + SignedMoney(g_longGross, currency));
SetLabelText("OSL_SHORT_GROSS", "回归毛利 " + SignedMoney(g_shortGross, currency));
SetLabelText("OSL_LONG_COST",
"点差 " + MoneyText(g_spreadCostMoney, currency) +
" + 佣金 " + MoneyText(g_commissionMoney, currency));
SetLabelText("OSL_SHORT_COST",
"点差 " + MoneyText(g_spreadCostMoney, currency) +
" + 佣金 " + MoneyText(g_commissionMoney, currency));
string lotsText = DoubleToString(MathMax(0.0, TradeLotsPerLeg), 2);
SetLabelText("OSL_LONG_BE",
"保本收敛 " + DoubleToString(g_breakEvenPrice, 3) +
" USD/bbl");
SetLabelText("OSL_SHORT_BE",
"保本收敛 " + DoubleToString(g_breakEvenPrice, 3) +
" USD/bbl");
SetLabelText("OSL_TRADE_LOTS",
"测算手数:每腿 " + lotsText + " 手 · 账户货币 " + currency);
}
//+------------------------------------------------------------------+
//| Update timeframe tab buttons |
//+------------------------------------------------------------------+
void UpdateTimeframeButtons()
{
StyleTimeframeButton("OSL_BTN_M15", g_analysisTf == PERIOD_M15);
StyleTimeframeButton("OSL_BTN_H1", g_analysisTf == PERIOD_H1);
StyleTimeframeButton("OSL_BTN_H4", g_analysisTf == PERIOD_H4);
}
//+------------------------------------------------------------------+
//| Style one timeframe button |
//+------------------------------------------------------------------+
void StyleTimeframeButton(const string name, const bool selected)
{
ObjectSetInteger(0, name, OBJPROP_STATE, false);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, (selected ? CLR_TEAL_DARK : CLR_CELL_ALT));
ObjectSetInteger(0, name, OBJPROP_COLOR, (selected ? CLR_TEXT : CLR_MUTED));
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, (selected ? CLR_TEAL : CLR_BORDER));
}
//+------------------------------------------------------------------+
//| Update unavailable symbols/data state |
//+------------------------------------------------------------------+
void UpdateUnavailableState()
{
g_stats.valid = false;
g_historyCount = 0;
SetLabelText("OSL_HEADER_TIME", "行情不可用");
SetLabelText("OSL_SYM1_PRICE", "--");
SetLabelText("OSL_SYM2_PRICE", "--");
SetLabelText("OSL_SYM1_QUOTE", "BID -- / ASK --");
SetLabelText("OSL_SYM2_QUOTE", "BID -- / ASK --");
SetLabelText("OSL_SYM1_CHANGE", "日变动 --");
SetLabelText("OSL_SYM2_CHANGE", "日变动 --");
SetLabelText("OSL_SPREAD_VALUE", "--");
SetLabelText("OSL_RATIO_VALUE", "--");
SetLabelText("OSL_PREMIUM_VALUE", "--");
SetLabelText("OSL_EXEC_SPREADS", "执行价差 --");
SetLabelText("OSL_STAT_MEAN_VALUE", "--");
SetLabelText("OSL_STAT_MEAN_SUB", "--");
SetLabelText("OSL_STAT_SD_VALUE", "--");
SetLabelText("OSL_STAT_SD_SUB", "--");
SetLabelText("OSL_STAT_Z_VALUE", "--");
SetLabelText("OSL_STAT_Z_SUB", "--");
SetLabelText("OSL_STAT_PCT_VALUE", "--");
SetLabelText("OSL_STAT_PCT_SUB", "--");
SetLabelText("OSL_STAT_HL_VALUE", "--");
SetLabelText("OSL_STAT_HL_SUB", "--");
SetLabelText("OSL_STAT_SLOPE_VALUE", "--");
SetLabelText("OSL_STAT_SLOPE_SUB", "--");
SetLabelText("OSL_SIGNAL_ACTION", "等待行情");
SetLabelColor("OSL_SIGNAL_ACTION", CLR_AMBER);
SetLabelText("OSL_SIGNAL_REASON",
"请确认 " + BrentSymbol + " 与 " + WtiSymbol +
" 已在市场报价中,并允许下载历史数据。");
SetLabelText("OSL_SIGNAL_STRENGTH", "--");
SetLabelText("OSL_FOOTER_STATUS",
"品种不可用或报价不完整 · 请检查交易品种名称");
SetLabelColor("OSL_FOOTER_STATUS", CLR_RED);
UpdateGauge();
UpdateTradeBlocks();
UpdateTimeframeButtons();
}
//+------------------------------------------------------------------+
//| Refresh footer data-quality status |
//+------------------------------------------------------------------+
void UpdateFooterStatus()
{
string barTime = (g_stats.valid && g_stats.lastBarTime > 0 ?
TimeToString(g_stats.lastBarTime, TIME_MINUTES) : "--:--");
string status = "样本 " + IntegerToString(g_stats.samples) +
" / " + IntegerToString(MathMax(20, MathMin(LookbackBars, 1000))) +
" · " + TimeframeName(g_analysisTf) +
" · K线至 " + barTime +
" · 更新 " + TimeToString(TimeCurrent(), TIME_MINUTES);
SetLabelText("OSL_FOOTER_STATUS", status);
SetLabelColor("OSL_FOOTER_STATUS", CLR_MUTED);
}
//+------------------------------------------------------------------+
//| Create rectangle label object |
//+------------------------------------------------------------------+
void CreateRect(const string name, const int x, const int y,
const int width, const int height,
const color background, const 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, PANEL_X + x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, PANEL_Y + y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, background);
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, name, OBJPROP_COLOR, border);
ObjectSetInteger(0, name, OBJPROP_BACK, false);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
//+------------------------------------------------------------------+
//| Create text label |
//+------------------------------------------------------------------+
void CreateLabel(const string name, const int x, const int y,
const string text, const int fontSize,
const color textColor, const string font,
const int anchor = ANCHOR_LEFT_UPPER)
{
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, PANEL_X + x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, PANEL_Y + y);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, name, OBJPROP_COLOR, textColor);
ObjectSetString(0, name, OBJPROP_FONT, font);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_BACK, false);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
//+------------------------------------------------------------------+
//| Create button object |
//+------------------------------------------------------------------+
void CreateButton(const string name, const int x, const int y,
const int width, const int height, const string text)
{
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, PANEL_X + x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, PANEL_Y + y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
ObjectSetString(0, name, OBJPROP_FONT, "Microsoft YaHei");
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, CLR_CELL_ALT);
ObjectSetInteger(0, name, OBJPROP_COLOR, CLR_MUTED);
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, CLR_BORDER);
ObjectSetInteger(0, name, OBJPROP_STATE, false);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
//+------------------------------------------------------------------+
//| Update label text |
//+------------------------------------------------------------------+
void SetLabelText(const string name, const string text)
{
ObjectSetString(0, name, OBJPROP_TEXT, text);
}
//+------------------------------------------------------------------+
//| Update label color |
//+------------------------------------------------------------------+
void SetLabelColor(const string name, const color textColor)
{
ObjectSetInteger(0, name, OBJPROP_COLOR, textColor);
}
//+------------------------------------------------------------------+
//| Update rectangle geometry |
//+------------------------------------------------------------------+
void SetRectGeometry(const string name, const int x, const int y,
const int width, const int height)
{
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, PANEL_X + x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, PANEL_Y + y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, MathMax(1, width));
ObjectSetInteger(0, name, OBJPROP_YSIZE, MathMax(1, height));
}
//+------------------------------------------------------------------+
//| Update rectangle colors |
//+------------------------------------------------------------------+
void SetRectColor(const string name, const color background, const color border)
{
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, background);
ObjectSetInteger(0, name, OBJPROP_COLOR, border);
}
//+------------------------------------------------------------------+
//| Delete only this EA's chart objects |
//+------------------------------------------------------------------+
void DeleteDashboard()
{
for(int i = ObjectsTotal(0, -1, -1) - 1; i >= 0; i--)
{
string name = ObjectName(0, i, -1, -1);
if(StringFind(name, OBJECT_PREFIX) == 0)
ObjectDelete(0, name);
}
}
//+------------------------------------------------------------------+
//| Format price with symbol digits |
//+------------------------------------------------------------------+
string FormatPrice(const double price, const int digits)
{
int useDigits = MathMax(0, MathMin(8, digits));
return DoubleToString(price, useDigits);
}
//+------------------------------------------------------------------+
//| Signed account-currency amount |
//+------------------------------------------------------------------+
string SignedMoney(const double value, const string currency)
{
string sign = (value > 0.0 ? "+" : "");
return sign + DoubleToString(value, 2) + " " + currency;
}
//+------------------------------------------------------------------+
//| Unsigned account-currency amount |
//+------------------------------------------------------------------+
string MoneyText(const double value, const string currency)
{
return DoubleToString(value, 2) + " " + currency;
}
//+------------------------------------------------------------------+
//| Timeframe display name |
//+------------------------------------------------------------------+
string TimeframeName(const int timeframe)
{
switch(timeframe)
{
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";
default: return "TF" + IntegerToString(timeframe);
}
}
//+------------------------------------------------------------------+