3475 lines
273 KiB
Plaintext
3475 lines
273 KiB
Plaintext
#property copyright "Copyright 2025, MARKT - Mark kasempast kumkaew"
|
|
#property link "https://www.facebook.com/elitetradersbymarkt"
|
|
#property version "1.0"
|
|
#property description "The Oracle Dashboard - By MARKT - VERSION_V1.0"
|
|
#property description "Contact : Elite Trader's Guild - EA by MARKT"
|
|
//+------------------------------------------------------------------+
|
|
//| Include Files |
|
|
//+------------------------------------------------------------------+
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\DealInfo.mqh>
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global Objects |
|
|
//+------------------------------------------------------------------+
|
|
CTrade trade;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Input Parameters |
|
|
//+------------------------------------------------------------------+
|
|
input group "โปรดอ่านคู่มือให้เข้าใจก่อนนะครับ (MARKT)"
|
|
input group "#### Broker Symbols & Magic Number ####"
|
|
// ปล่อยว่างไว้ = ให้ระบบตรวจจับอัตโนมัติจาก Market Watch (เช่น EURUSD.sc -> suffix = ".sc")
|
|
input string InpSymbolSuffix = ""; //SymbalSuffix
|
|
input ulong InpMagicNumber = 112233; //MagicNumber
|
|
|
|
input group "#### Logic Intensity ####"
|
|
input double InpLogicUsePercent = 80.0; //LogicPercent
|
|
|
|
//input group "----Realtime----"
|
|
const bool InpRealtimeEnable = true; // อัปเดตระดับมิลลิวินาที
|
|
const int InpRefreshMs = 200; // ความถี่อัปเดตข้อมูล (ms)
|
|
const int InpUiUpdateMs = 150; // ความถี่รีเฟรช UI (ms)
|
|
const bool InpCalcOnClosedBar = true;
|
|
|
|
input group "#### Execution Control ####"
|
|
input int InpRowCooldownBars = 2; //Cooldown
|
|
input int InpMaxPositionsPerRow = 2; // max orders per row (2 = คู่พื้นฐาน)
|
|
input double InpHighlightSignalAtPercent = 60.0; //HighlightSignalPercent
|
|
|
|
|
|
input group "#### UI & Scaling Settings ####"
|
|
input bool InpAutoResize = true;
|
|
input double InpManualScale = 1.0;
|
|
input int InpBaseFontSize = 9;
|
|
|
|
input ENUM_TIMEFRAMES InpTimeframe_Confirmation = PERIOD_H4; //ConfirmTimeframe
|
|
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1; //MainTimeframe
|
|
input int InpLookback = 220; //LookbackCandle
|
|
input int InpRefreshSec = 1; //RefreshSec
|
|
|
|
input group "#### SL/TP Settings ####"
|
|
input double InpRR_TP_to_SL = 1.50; // Risk:Reward
|
|
|
|
input group "#### Money Management ####"
|
|
input double InpLotSize = 0.03; //LotSize(Fixed Lot)
|
|
input double InpRiskPercent = 1.5; //RiskPercent (Auto Risk%)
|
|
input int InpATR_Period = 14; //ATR-Period
|
|
input double InpATR_Multiplier= 2.0; //ATR-Multiplier
|
|
input int InpADX_Period = 14; //ADX-Period
|
|
input double InpADX_TrendLevel= 25.0; //ADX-TrendLevel
|
|
|
|
input group "#### Basket Exit Strategies ####"
|
|
input double InpBasketTakeProfitPercent = 5.0; //BasketTakeProfitPercent
|
|
input double InpBasketStopLossPercent = 2.5; //BasketStopLossPercent
|
|
input double InpBasketTakeProfitMoney = 50.0; //BasketTakeProfitMoney
|
|
input double InpBasketStopLossMoney = 25.0; //BasketStopLossMoney
|
|
|
|
input group "#### Individual Exit Strategies ####"
|
|
input int InpMaxBarsHold = 150; //MaxBars
|
|
input double InpCorrDecayThreshold = 0.3; //CorrDecayThreshold
|
|
|
|
input group "#### Core Settings ####"
|
|
input bool InpAutoConfirm = false; //AutoTrade
|
|
input double InpMinConfidenceForAutoTrade = 65.0; //MinCONF%
|
|
input double InpMinAbsCorrForOpportunity = 0.60; //MinCorr (Opportunity)
|
|
|
|
input group "#### Force Auto Entry ####"
|
|
input bool InpForceAuto = false; //EnableFoceAutoTrade
|
|
input bool InpForceAuto_OnSignalOnly = false; //ForceAuto_OnSignalOnly
|
|
input double InpForceAutoMinConf = 70.0; //ForceAutoMinConf
|
|
input bool InpForceAutoBypassFilters = false; //ForceAutoBypassFilters
|
|
|
|
// ----Row Auto Close (TP)----
|
|
enum ENUM_ROW_TP_MODE { ROW_TP_MODE_PERCENT, ROW_TP_MODE_MONEY };
|
|
input group "#### Row Auto Close (TP) ####"
|
|
input bool InpRowAutoTP_Enable = false; //EnableAutoCloseROW
|
|
input ENUM_ROW_TP_MODE InpRowTPMode = ROW_TP_MODE_MONEY; //ROW-TP-Mode
|
|
input double InpRowTP_Percent = 0.80; //RowTP-Percent
|
|
input double InpRowTP_Money = 10.0; //RowTP-Money
|
|
|
|
// ----Opportunity Auto Close (TP)----
|
|
enum ENUM_OPP_TP_MODE { OPP_TP_MODE_PERCENT, OPP_TP_MODE_MONEY };
|
|
input bool InpOppAutoTP_Enable = false; //EnableAutoCloseOpportunity
|
|
input ENUM_OPP_TP_MODE InpOppTPMode = OPP_TP_MODE_MONEY; //Opportunity-TP-Mode
|
|
input double InpOppTP_Percent = 0.50; //Opportunity-Percent
|
|
input double InpOppTP_Money = 8.0; //Opportunity-Money
|
|
|
|
input group "#### Signal Filters ####"
|
|
input bool InpUsePreFilters = true; //PreFillters
|
|
input double InpMaxSpreadPoints = 30; //MaxSpreadPoint
|
|
input double InpMinATRPoints = 10; //MinATRPoints
|
|
input bool InpRequireHAFreshFlip = false; //EnableRequireHAFreshFlip
|
|
input int InpHAFreshBars = 3; //HAFreshBars
|
|
|
|
// ----Opportunity Finder Mode----
|
|
enum ENUM_OPP_MODE
|
|
{
|
|
OPP_MODE_MEANREVERT,
|
|
OPP_MODE_SWING,
|
|
OPP_MODE_SCALP,
|
|
OPP_MODE_AUTO
|
|
};
|
|
input group "#### Opportunity Finder Mode ####"
|
|
input ENUM_OPP_MODE InpOppMode = OPP_MODE_MEANREVERT; //OF-Mode
|
|
|
|
// ----Correlation ZScore----
|
|
input group "#### Correlation ZScore ####"
|
|
input double InpCorrPosThreshold = 0.60; //CorrPosThreshold
|
|
input double InpCorrNegThreshold = -0.60; //CorrNegThreshold
|
|
input double InpZSpikeEnter = 1.20; //ZSpikeEnter
|
|
input double InpZSpikeExit = 0.80; //ZSpikeExit
|
|
|
|
// ----Basket Trailing (new)----
|
|
input group "#### Basket Trailing ####"
|
|
input bool InpEnableBasketTrailing = false; //EnableBasketTrailing
|
|
input double InpBasketTrailArmPercent = 3.0; //BasketTrailArmPercent
|
|
input double InpBasketTrailStepPercent = 1.0; //BasketTrailStepPercent
|
|
const int InpRightPanelShiftX = 0;
|
|
|
|
const bool InpForceManualButtons = true;
|
|
const double InpRiskMismatchWarnPct = 30.0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Enums and Structs |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_BASKET_EXIT_MODE { MODE_PERCENT, MODE_MONEY };
|
|
enum ENUM_SIGNAL_TYPE { SIGNAL_NONE, SIGNAL_BB, SIGNAL_SS, SIGNAL_BS, SIGNAL_SB };
|
|
enum ENUM_LOT_SIZING_MODE { LOT_MODE_FIXED, LOT_MODE_AUTO };
|
|
|
|
struct CorrelationPairInfo { string symbol1; string symbol2; double current_corr; double avg_corr; double delta_corr; double history[]; };
|
|
struct SymbolPriceCache { string name; double prices[]; };
|
|
struct SignalInfo
|
|
{
|
|
ENUM_SIGNAL_TYPE type;
|
|
double confidence;
|
|
SignalInfo() : type(SIGNAL_NONE), confidence(0.0) {}
|
|
SignalInfo(const SignalInfo &other) : type(other.type), confidence(other.confidence) {}
|
|
};
|
|
struct OpportunityInfo
|
|
{
|
|
string symbol;
|
|
double correlation;
|
|
string style;
|
|
ENUM_SIGNAL_TYPE trade_action;
|
|
double confidence; // NEW
|
|
|
|
OpportunityInfo() : correlation(0), trade_action(SIGNAL_NONE), confidence(0.0) {}
|
|
OpportunityInfo(const OpportunityInfo &other) :
|
|
symbol(other.symbol),
|
|
correlation(other.correlation),
|
|
style(other.style),
|
|
trade_action(other.trade_action),
|
|
confidence(other.confidence) {}
|
|
};
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global Constants |
|
|
//+------------------------------------------------------------------+
|
|
#define TOTAL_PAIRS 20
|
|
const int CORR_HISTORY_SIZE = 20;
|
|
|
|
//--- Market Session Hours (GMT/UTC)
|
|
const int SYDNEY_OPEN = 22;
|
|
const int SYDNEY_CLOSE = 7;
|
|
const int TOKYO_OPEN = 0;
|
|
const int TOKYO_CLOSE = 9;
|
|
const int LONDON_OPEN = 8;
|
|
const int LONDON_CLOSE = 17;
|
|
const int NY_OPEN = 13;
|
|
const int NY_CLOSE = 22;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| UI Layout Variables (Dynamic) |
|
|
//+------------------------------------------------------------------+
|
|
|
|
const color c_main_bg = clrWhiteSmoke;
|
|
const color c_bg_dark = C'30, 45, 70';
|
|
const color c_bg_light = C'40, 60, 90';
|
|
const color c_header_bg = C'10, 20, 40';
|
|
const color c_font_color = clrWhiteSmoke;
|
|
const color c_edit_bg = C'20, 40, 60';
|
|
const color c_edit_border = clrWhiteSmoke;
|
|
const color c_btn_off_bg = C'128,0,0';
|
|
const color c_btn_on_bg = C'0,128,0';
|
|
const color c_btn_bb_ss_bg = C'0, 80, 170';
|
|
const color c_btn_bs_sb_bg = C'170, 80, 0';
|
|
const color c_btn_highlight_bg = clrGold;
|
|
|
|
|
|
double g_scale = 1.0;
|
|
int g_curr_font_size = 8;
|
|
|
|
int c_row_h;
|
|
int c_margin;
|
|
|
|
int c_col_close_w;
|
|
int c_col_profit_w;
|
|
int c_col_order_w;
|
|
int c_col_entry_buy_w;
|
|
int c_col_auto_w;
|
|
int c_col_entry_sell_w;
|
|
int c_col_corr_w;
|
|
int c_col_current_corr_w;
|
|
int c_col_delta_corr_w;
|
|
int c_col_sprd_div_w;
|
|
int c_col_confidence_w;
|
|
int c_info_panel_w;
|
|
|
|
int c_col_close_x;
|
|
int c_col_profit_x;
|
|
int c_col_order_x;
|
|
int c_col_entry_buy_x;
|
|
int c_col_auto_x;
|
|
int c_col_entry_sell_x;
|
|
int c_col_corr_x;
|
|
int c_col_current_corr_x;
|
|
int c_col_delta_corr_x;
|
|
int c_col_sprd_div_x;
|
|
int c_col_confidence_x;
|
|
|
|
int c_main_panel_w;
|
|
int c_info_panel_x;
|
|
int c_total_panel_w;
|
|
|
|
|
|
const int BASE_ROW_H = 25;
|
|
const int BASE_MARGIN = 0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global Variables |
|
|
//+------------------------------------------------------------------+
|
|
string g_object_prefix = "AdvDash_";
|
|
string g_pairs[TOTAL_PAIRS][2] = {
|
|
{"EURUSD","USDCHF"}, {"GBPUSD","USDJPY"}, {"AUDUSD","USDCAD"}, {"EURGBP","GBPUSD"},
|
|
{"USDJPY","EURCHF"}, {"USDCAD","AUDCHF"}, {"GBPCHF","USDJPY"}, {"AUDJPY","USDCAD"},
|
|
{"EURAUD","AUDUSD"}, {"GBPJPY","USDCHF"}, {"EURNZD","NZDUSD"}, {"CADJPY","USDCHF"},
|
|
{"CHFJPY","USDCHF"}, {"EURCAD","CADJPY"}, {"GBPAUD","AUDUSD"}, {"AUDNZD","NZDUSD"},
|
|
{"EURJPY","USDCAD"}, {"GBPCAD","USDCAD"}, {"AUDCHF","USDCHF"}, {"NZDJPY","USDJPY"}
|
|
};
|
|
bool g_auto_status[TOTAL_PAIRS];
|
|
CorrelationPairInfo g_corr_data[TOTAL_PAIRS];
|
|
SymbolPriceCache g_price_cache[];
|
|
string g_matrix_symbols[] = {"EUR","USD","JPY","GBP","AUD","NZD","CAD","CHF","XAU"};
|
|
double g_matrix_corr[9][9];
|
|
ENUM_LOT_SIZING_MODE g_lot_mode = LOT_MODE_FIXED;
|
|
bool g_use_time_exit = false;
|
|
bool g_use_corr_decay_exit = false;
|
|
bool g_basket_exit_enabled = false;
|
|
ENUM_BASKET_EXIT_MODE g_basket_exit_mode = MODE_PERCENT;
|
|
bool g_use_mtf_filter = false;
|
|
ENUM_TIMEFRAMES g_current_timeframe;
|
|
OpportunityInfo g_opportunities[5];
|
|
int g_ha_handles_main[TOTAL_PAIRS][2];
|
|
int g_ha_handles_conf[TOTAL_PAIRS][2];
|
|
datetime g_last_cache_time[];
|
|
int g_timer_counter = 0;
|
|
double g_basket_tp_percent;
|
|
double g_basket_sl_percent;
|
|
double g_basket_tp_money;
|
|
double g_basket_sl_money;
|
|
double g_fixed_lot_size;
|
|
double g_risk_percent;
|
|
int g_max_bars_hold;
|
|
double g_corr_decay_threshold;
|
|
string g_last_corr_text[TOTAL_PAIRS];
|
|
string g_last_delta_text[TOTAL_PAIRS];
|
|
string g_last_conf_text[TOTAL_PAIRS];
|
|
string g_last_sprd_text[TOTAL_PAIRS];
|
|
color g_last_corr_color[TOTAL_PAIRS];
|
|
color g_last_delta_color[TOTAL_PAIRS];
|
|
ENUM_SIGNAL_TYPE g_last_highlight[TOTAL_PAIRS];
|
|
int g_active_graph_row = -1;
|
|
bool g_use_sl_tp = false;
|
|
double g_rr = 1.5;
|
|
string g_symbol_suffix = "";
|
|
datetime g_last_row_close_bar[TOTAL_PAIRS];
|
|
datetime g_row_last_hist_bar[TOTAL_PAIRS];
|
|
int g_adx_handles_main[TOTAL_PAIRS][2];
|
|
bool g_silent_force = false;
|
|
enum ENUM_EXEC_CTX { EXEC_MANUAL, EXEC_AUTO, EXEC_FORCE_AUTO };
|
|
ENUM_EXEC_CTX g_exec_ctx = EXEC_MANUAL;
|
|
//+------------------------------------------------------------------+
|
|
//| Function Prototypes |
|
|
//+------------------------------------------------------------------+
|
|
void CreateDashboardUI();
|
|
void UpdateInfoPanel();
|
|
void UpdateDashboardData();
|
|
void CreateCellWithText(string name, string text, int x, int y, int w, int h, color bg_clr, color text_clr, int font_size, ENUM_ANCHOR_POINT anchor, string font="Arial", ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER);
|
|
void CreateRectangle(string name, int x, int y, int w, int h, color clr, ENUM_BASE_CORNER corner, ENUM_BORDER_TYPE border=BORDER_FLAT, bool is_background=true);
|
|
void CreateLabel(string name, string text, int x, int y, color clr, ENUM_ANCHOR_POINT anchor=ANCHOR_LEFT, string font="Arial", int font_size=-1, ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER);
|
|
void CreateButton(string name, string text, int x, int y, int w, int h, color bg_clr, ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER);
|
|
void CreateEditBox(string name, string text, int x, int y, int w, int h, ENUM_ALIGN_MODE align=ALIGN_RIGHT);
|
|
void UpdateAutoButton(int index);
|
|
void BuildUniqueSymbolListAndPrepareCache();
|
|
bool CacheAllSymbolData();
|
|
bool GetPricesFromCache(string symbol, double &prices[]);
|
|
double CalculatePearsonCorrelation(const double &x[], const double &y[], int count);
|
|
double ArraySum(const double &arr[]);
|
|
double ArrayMean(const double &arr[]);
|
|
void ArrayRemove(double &arr[], int start_index, int count);
|
|
void CreateTopPanels(int start_x, int start_y);
|
|
void CreateBottomPanels(int start_x, int start_y);
|
|
void UpdateTimePanel();
|
|
double CalculateClosedProfit();
|
|
void CreateMainListDashboard(int &width, int &height);
|
|
void CreateCorrelationMatrix(int start_x, int start_y);
|
|
void CreateSignalPanels(int start_x, int start_y);
|
|
void CreateBasketPanel(int start_x, int start_y, int panel_w);
|
|
void CreateTradeSettingsPanel(int start_x, int start_y, int panel_w);
|
|
void CreateOpportunityPanel(int start_x, int start_y);
|
|
void CreateHeader(int y_pos);
|
|
void CreateDataRow(int index, int y_pos);
|
|
void CreateInfoPanel();
|
|
SignalInfo CheckTradingSignals(int index);
|
|
double CalculateSignalConfidence(double correlation, double delta_corr);
|
|
void UpdateSignalHighlight(int index, string button_to_highlight);
|
|
void ResetAllHighlights(int index);
|
|
void ExecuteTrade(int index, ENUM_SIGNAL_TYPE signal_type, bool allow_force=false);
|
|
void ExecuteMatrixTrade(string symbol, ENUM_SIGNAL_TYPE trade_action);
|
|
string SignalTypeToString(ENUM_SIGNAL_TYPE signal_type);
|
|
void ManageOpenTrades();
|
|
void UpdateAndManageBasket();
|
|
void CloseAllBasketTrades();
|
|
void CloseProfitableBasketTrades();
|
|
double CalculateLotSize(string symbol);
|
|
int GetRowIndexFromSymbol(string symbol);
|
|
string GetSymbolForCurrencies(string ccy1, string ccy2);
|
|
void UpdateCorrelationMatrix();
|
|
void FindMatrixOpportunities();
|
|
void UpdateOpportunityPanel();
|
|
void RecreateIndicatorHandles();
|
|
bool UpdatePriceCache();
|
|
void AddOpp(OpportunityInfo &arr[], int &count,
|
|
const string sym, const double corr,
|
|
const string style, const ENUM_SIGNAL_TYPE sig);
|
|
|
|
void UpdatePairTradeStatus();
|
|
void ClosePairTrades(int row_index);
|
|
void CloseAllProfitableTrades();
|
|
void CloseAllLosingTrades();
|
|
void CreateMainBackground();
|
|
bool IsSessionOpen(int open_hour, int close_hour, int current_hour_gmt);
|
|
void CreateAnalysisPanel(int start_x, int start_y);
|
|
void CloseCorrelationGraph();
|
|
void ShowCorrelationGraph(int row_index);
|
|
double CalcOpportunityConfidence(const string symbol, const double corr, const ENUM_SIGNAL_TYPE action);
|
|
void UpdateRowConfAndHighlight(const int index);
|
|
int GetMatrixPixelHeight();
|
|
double AdjustConfidenceByLogic(double conf);
|
|
double SmoothStep01(double t);
|
|
double SoftCap100(double x, double k);
|
|
double GetSpreadPts(const string sym);
|
|
bool IsMajorSessionOpen();
|
|
void ManageAutoCloseRowsAndOpp();
|
|
void CloseMatrixSymbolTrades(const string symbol);
|
|
void NotifyUser(const string msg);
|
|
void FindMatrixOpportunities();
|
|
void ComputeMatrixStrength(double &strength[]);
|
|
bool LegsForCurrencies(const string a, const string b, string &legA, string &legB);
|
|
bool ZSpreadByLegs(const string ccy1, const string ccy2, double &z, ENUM_SIGNAL_TYPE &sig);
|
|
|
|
string LegLabel(const ENUM_ORDER_TYPE t);
|
|
bool PreflightDeal(const string sym, ENUM_ORDER_TYPE typ, double vol, double &sl, double &tp,
|
|
string &reason, uint &retcodeOut);
|
|
bool SendDealRawEx(const string sym, const ENUM_ORDER_TYPE typ, double vol, double sl, double tp,
|
|
const string &cmt, uint &retcodeOut, string &commentOut);
|
|
|
|
|
|
|
|
double CalcOpportunityConfidence(const string symbol, const double corr, const ENUM_SIGNAL_TYPE action)
|
|
{
|
|
|
|
double absCorr = MathAbs(corr);
|
|
double t0 = InpMinAbsCorrForOpportunity;
|
|
double t = (absCorr - t0) / MathMax(1e-6, 1.0 - t0);
|
|
double base = 60.0 * SmoothStep01(t);
|
|
double align = 0.0;
|
|
ENUM_SIGNAL_TYPE dir;
|
|
if(SingleSymbolDirection(symbol, dir))
|
|
align = (dir == action ? 12.0 : 4.0);
|
|
|
|
double sprPts = GetSpreadPts(symbol);
|
|
double sprLim = MathMax(1.0, GetSpreadLimit(symbol));
|
|
double sprRatio= sprPts / sprLim;
|
|
double spread_pen = (sprRatio > 1.0) ? -12.0 * MathMin(1.0, sprRatio - 1.0) : 0.0;
|
|
|
|
|
|
double atr_bonus = 0.0;
|
|
int h = iATR(symbol, g_current_timeframe, InpATR_Period);
|
|
double b[]; double pt = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
if(h != INVALID_HANDLE && CopyBuffer(h,0,0,2,b) == 2 && pt > 0.0)
|
|
{
|
|
double atr_pts = b[1] / pt;
|
|
double r = atr_pts / MathMax(1.0, InpMinATRPoints);
|
|
double rr = Clamp01((r - 1.0) / 2.0);
|
|
atr_bonus = -4.0 + rr * (8.0 + 4.0);
|
|
}
|
|
double sess = IsMajorSessionOpen() ? 6.0 : -3.0;
|
|
double pf = PassesPreTradeFilters(symbol) ? 5.0 : -6.0;
|
|
double raw = base + align + atr_bonus + sess + pf + spread_pen;
|
|
double conf = SoftCap100(MathMax(0.0, raw), 85.0);
|
|
conf = AdjustConfidenceByLogic(conf);
|
|
if(conf > 97.0) conf = 97.0;
|
|
if(conf < 3.0) conf = 3.0;
|
|
return conf;
|
|
}
|
|
|
|
|
|
|
|
void UpdateRowConfAndHighlight(const int index)
|
|
{
|
|
|
|
SignalInfo sig = CheckTradingSignals(index);
|
|
string conf_text = StringFormat("%.1f%%", sig.confidence);
|
|
if(g_last_conf_text[index] != conf_text)
|
|
{
|
|
ObjectSetString(0, g_object_prefix + "CONF_CELL_" + (string)index + "_TXT", OBJPROP_TEXT, conf_text);
|
|
g_last_conf_text[index] = conf_text;
|
|
}
|
|
color conf_color = (sig.confidence >= 70.0) ? C'30,255,150'
|
|
: (sig.confidence >= 40.0 ? clrOrange : C'255,99,71');
|
|
ObjectSetInteger(0, g_object_prefix + "CONF_CELL_" + (string)index + "_TXT", OBJPROP_COLOR, conf_color);
|
|
if(sig.type != SIGNAL_NONE && sig.confidence >= InpHighlightSignalAtPercent)
|
|
{
|
|
if(g_last_highlight[index] != sig.type)
|
|
{
|
|
UpdateSignalHighlight(index, SignalTypeToString(sig.type));
|
|
g_last_highlight[index] = sig.type;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(g_last_highlight[index] != SIGNAL_NONE)
|
|
{
|
|
ResetAllHighlights(index);
|
|
g_last_highlight[index] = SIGNAL_NONE;
|
|
}
|
|
}
|
|
|
|
if(g_auto_status[index] && sig.type != SIGNAL_NONE)
|
|
{
|
|
if(HasOpenForRow(index)) return;
|
|
if(InRowCooldown(index)) return;
|
|
bool normal_auto_ready =
|
|
(InpAutoConfirm && sig.confidence >= InpMinConfidenceForAutoTrade);
|
|
bool force_auto_ready = false;
|
|
if(InpForceAuto)
|
|
{
|
|
if(InpForceAuto_OnSignalOnly) force_auto_ready = true;
|
|
else if(sig.confidence >= InpForceAutoMinConf) force_auto_ready = true;
|
|
}
|
|
|
|
if(normal_auto_ready || force_auto_ready)
|
|
{
|
|
ENUM_EXEC_CTX prev_ctx = g_exec_ctx;
|
|
g_exec_ctx = (force_auto_ready ? EXEC_FORCE_AUTO : EXEC_AUTO);
|
|
|
|
if(force_auto_ready && InpForceAutoBypassFilters)
|
|
{
|
|
bool prev_silent = g_silent_force;
|
|
g_silent_force = true;
|
|
ExecuteTrade(index, sig.type, true);
|
|
g_silent_force = prev_silent;
|
|
}
|
|
else
|
|
{
|
|
ExecuteTrade(index, sig.type);
|
|
}
|
|
g_exec_ctx = prev_ctx;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
|
|
UpdateDynamicLayout();
|
|
|
|
DetectSymbolSuffix(g_symbol_suffix);
|
|
Print("Symbol suffix in use: '", g_symbol_suffix, "'");
|
|
g_current_timeframe = InpTimeframe;
|
|
ApplySuffixToPairs();
|
|
g_basket_tp_percent = InpBasketTakeProfitPercent;
|
|
g_basket_sl_percent = InpBasketStopLossPercent;
|
|
g_basket_tp_money = InpBasketTakeProfitMoney;
|
|
g_basket_sl_money = InpBasketStopLossMoney;
|
|
g_fixed_lot_size = InpLotSize;
|
|
g_risk_percent = InpRiskPercent;
|
|
g_max_bars_hold = InpMaxBarsHold;
|
|
g_corr_decay_threshold= InpCorrDecayThreshold;
|
|
g_rr = InpRR_TP_to_SL;
|
|
|
|
|
|
ArrayInitialize(g_auto_status, false);
|
|
ArrayInitialize(g_ha_handles_main, -1);
|
|
ArrayInitialize(g_ha_handles_conf, -1);
|
|
ArrayInitialize(g_row_last_hist_bar, 0);
|
|
ArrayInitialize(g_adx_handles_main, -1);
|
|
|
|
for(int i = 0; i < TOTAL_PAIRS; i++)
|
|
{
|
|
g_corr_data[i].symbol1 = g_pairs[i][0];
|
|
g_corr_data[i].symbol2 = g_pairs[i][1];
|
|
ArrayResize(g_corr_data[i].history, 0);
|
|
g_last_corr_text[i] = "";
|
|
g_last_delta_text[i] = "";
|
|
g_last_conf_text[i] = "";
|
|
g_last_sprd_text[i] = "";
|
|
g_last_corr_color[i] = clrNONE;
|
|
g_last_delta_color[i]= clrNONE;
|
|
g_last_highlight[i] = SIGNAL_NONE;
|
|
}
|
|
|
|
BuildUniqueSymbolListAndPrepareCache();
|
|
RecreateIndicatorHandles();
|
|
|
|
CreateDashboardUI();
|
|
trade.SetExpertMagicNumber(InpMagicNumber);
|
|
EventSetTimer(MathMax(1, InpRefreshSec));
|
|
|
|
OnTimer();
|
|
UpdateCorrelationMatrix();
|
|
FindMatrixOpportunities();
|
|
UpdateOpportunityPanel();
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
EventKillTimer();
|
|
|
|
for(int i = 0; i < TOTAL_PAIRS; i++)
|
|
{
|
|
IndicatorRelease(g_ha_handles_main[i][0]);
|
|
IndicatorRelease(g_ha_handles_main[i][1]);
|
|
IndicatorRelease(g_ha_handles_conf[i][0]);
|
|
IndicatorRelease(g_ha_handles_conf[i][1]);
|
|
if(g_adx_handles_main[i][0] != INVALID_HANDLE) IndicatorRelease(g_adx_handles_main[i][0]);
|
|
if(g_adx_handles_main[i][1] != INVALID_HANDLE) IndicatorRelease(g_adx_handles_main[i][1]);
|
|
}
|
|
ObjectsDeleteAll(0, g_object_prefix);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert chart event function (Full Version with Auto-Resize) |
|
|
//+------------------------------------------------------------------+
|
|
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
|
{
|
|
// --- 1. ส่วนดักจับการเปลี่ยนขนาดหน้าจอ (Auto-Resize) ---
|
|
if(id == CHARTEVENT_CHART_CHANGE)
|
|
{
|
|
UpdateDynamicLayout();
|
|
CreateDashboardUI();
|
|
ChartRedraw();
|
|
return;
|
|
}
|
|
|
|
|
|
if(id == CHARTEVENT_OBJECT_CLICK)
|
|
{
|
|
|
|
if(g_active_graph_row != -1 && StringFind(sparam, "Graph_") < 0)
|
|
{
|
|
CloseCorrelationGraph();
|
|
return;
|
|
}
|
|
|
|
|
|
if(StringFind(sparam, "Graph_CloseBtn") >= 0)
|
|
{
|
|
CloseCorrelationGraph();
|
|
return;
|
|
}
|
|
|
|
string base_name = StringSubstr(sparam, StringLen(g_object_prefix));
|
|
string parts[];
|
|
StringSplit(base_name, '_', parts);
|
|
|
|
|
|
if(StringFind(sparam, "CTRL_") >= 0)
|
|
{
|
|
|
|
if(StringFind(sparam, "CTRL_CLOSE_PROFIT_BTN") > 0) { CloseAllProfitableTrades(); }
|
|
else if(StringFind(sparam, "CTRL_CLOSE_LOSS_BTN") > 0) { CloseAllLosingTrades(); }
|
|
else if(StringFind(sparam, "CTRL_CLOSE_ALL_BTN") > 0) { CloseAllBasketTrades(); }
|
|
|
|
|
|
else if(sparam == g_object_prefix + "CTRL_BASKET_BTN_MODE_PERCENT")
|
|
{
|
|
g_basket_exit_mode = MODE_PERCENT;
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, c_btn_on_bg);
|
|
ObjectSetInteger(0, g_object_prefix + "CTRL_BASKET_BTN_MODE_MONEY", OBJPROP_BGCOLOR, c_bg_light);
|
|
}
|
|
else if(sparam == g_object_prefix + "CTRL_BASKET_BTN_MODE_MONEY")
|
|
{
|
|
g_basket_exit_mode = MODE_MONEY;
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, c_btn_on_bg);
|
|
ObjectSetInteger(0, g_object_prefix + "CTRL_BASKET_BTN_MODE_PERCENT", OBJPROP_BGCOLOR, c_bg_light);
|
|
}
|
|
|
|
|
|
else if(sparam == g_object_prefix + "CTRL_BASKET_BTN_ENABLE")
|
|
{
|
|
g_basket_exit_enabled = !g_basket_exit_enabled;
|
|
ObjectSetString(0, sparam, OBJPROP_TEXT, g_basket_exit_enabled ? "BASKET EXIT IS ON" : "ENABLE BASKET EXIT");
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, g_basket_exit_enabled ? c_btn_on_bg : c_btn_off_bg);
|
|
}
|
|
|
|
|
|
else if(sparam == g_object_prefix + "CTRL_TRADE_SETTINGS_BTN_LOT_FIXED")
|
|
{
|
|
g_lot_mode = LOT_MODE_FIXED;
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, c_btn_on_bg);
|
|
ObjectSetInteger(0, g_object_prefix + "CTRL_TRADE_SETTINGS_BTN_LOT_AUTO", OBJPROP_BGCOLOR, c_btn_off_bg);
|
|
}
|
|
else if(sparam == g_object_prefix + "CTRL_TRADE_SETTINGS_BTN_LOT_AUTO")
|
|
{
|
|
g_lot_mode = LOT_MODE_AUTO;
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, c_btn_on_bg);
|
|
ObjectSetInteger(0, g_object_prefix + "CTRL_TRADE_SETTINGS_BTN_LOT_FIXED", OBJPROP_BGCOLOR, c_btn_off_bg);
|
|
}
|
|
|
|
|
|
else if(sparam == g_object_prefix + "CTRL_TRADE_SETTINGS_BTN_TIME_EXIT")
|
|
{
|
|
g_use_time_exit = !g_use_time_exit;
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, g_use_time_exit ? c_btn_on_bg : c_btn_off_bg);
|
|
}
|
|
else if(sparam == g_object_prefix + "CTRL_TRADE_SETTINGS_BTN_CORR_DECAY")
|
|
{
|
|
g_use_corr_decay_exit = !g_use_corr_decay_exit;
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, g_use_corr_decay_exit ? c_btn_on_bg : c_btn_off_bg);
|
|
}
|
|
else if(sparam == g_object_prefix + "CTRL_TRADE_SETTINGS_BTN_MTF_FILTER")
|
|
{
|
|
g_use_mtf_filter = !g_use_mtf_filter;
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, g_use_mtf_filter ? c_btn_on_bg : c_btn_off_bg);
|
|
}
|
|
else if(sparam == g_object_prefix + "CTRL_TRADE_SETTINGS_BTN_SLTP")
|
|
{
|
|
g_use_sl_tp = !g_use_sl_tp;
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, g_use_sl_tp ? c_btn_on_bg : c_btn_off_bg);
|
|
string cap = g_use_sl_tp ? "SL/TP (ON • RR " + StringFormat("%.1f", g_rr) + ")" : "SL/TP (OFF)";
|
|
ObjectSetString(0, sparam, OBJPROP_TEXT, cap);
|
|
}
|
|
|
|
|
|
else if(StringFind(sparam, "RELOAD") >= 0)
|
|
{
|
|
OnTimer();
|
|
}
|
|
|
|
|
|
else if(StringFind(sparam, "TF_") > 0)
|
|
{
|
|
string tfs_lbl[] = {"M15","M30","H1","H4","D1"};
|
|
for(int kk=0; kk<ArraySize(tfs_lbl); kk++)
|
|
ObjectSetInteger(0, g_object_prefix + "CTRL_TF_" + tfs_lbl[kk], OBJPROP_BGCOLOR, c_bg_light);
|
|
|
|
if(StringFind(sparam, "M15") > 0) g_current_timeframe = PERIOD_M15;
|
|
else if(StringFind(sparam, "M30") > 0) g_current_timeframe = PERIOD_M30;
|
|
else if(StringFind(sparam, "H1") > 0) g_current_timeframe = PERIOD_H1;
|
|
else if(StringFind(sparam, "H4") > 0) g_current_timeframe = PERIOD_H4;
|
|
else if(StringFind(sparam, "D1") > 0) g_current_timeframe = PERIOD_D1;
|
|
|
|
ObjectSetInteger(0, sparam, OBJPROP_BGCOLOR, c_btn_on_bg);
|
|
RecreateIndicatorHandles();
|
|
OnTimer();
|
|
}
|
|
}
|
|
|
|
else if(StringFind(sparam, "ANALYSIS_OPP_TRADE_BTN_") >= 0)
|
|
{
|
|
StringSplit(StringSubstr(sparam, StringLen(g_object_prefix + "ANALYSIS_OPP_TRADE_BTN_")), '_', parts);
|
|
int opp_index = (int)StringToInteger(parts[0]);
|
|
if(opp_index < ArraySize(g_opportunities) && g_opportunities[opp_index].trade_action != SIGNAL_NONE)
|
|
ExecuteMatrixTrade(g_opportunities[opp_index].symbol, g_opportunities[opp_index].trade_action);
|
|
}
|
|
|
|
else if(StringFind(sparam, "_CLOSE_BTN_") > 0)
|
|
{
|
|
int row_index = (int)StringToInteger(parts[ArraySize(parts) - 1]);
|
|
if (row_index != -1) ClosePairTrades(row_index);
|
|
}
|
|
|
|
else if(StringFind(sparam, "CURR_CORR_CELL_") >= 0 || StringFind(sparam, "DELTA_CORR_CELL_") >= 0)
|
|
{
|
|
int row_index = (int)StringToInteger(parts[ArraySize(parts) - 1]);
|
|
if (row_index != -1) ShowCorrelationGraph(row_index);
|
|
}
|
|
|
|
else
|
|
{
|
|
int row_index = -1;
|
|
if(ArraySize(parts) > 0) row_index = (int)StringToInteger(parts[ArraySize(parts) - 1]);
|
|
|
|
if(row_index != -1)
|
|
{
|
|
if(StringFind(sparam, "_AUTO_BTN_") > 0)
|
|
{
|
|
g_auto_status[row_index] = !g_auto_status[row_index];
|
|
UpdateAutoButton(row_index);
|
|
}
|
|
else if(StringFind(sparam, "_BB_BTN_") > 0) { ExecuteTrade(row_index, SIGNAL_BB, InpForceManualButtons); }
|
|
else if(StringFind(sparam, "_SS_BTN_") > 0) { ExecuteTrade(row_index, SIGNAL_SS, InpForceManualButtons); }
|
|
else if(StringFind(sparam, "_BS_BTN_") > 0) { ExecuteTrade(row_index, SIGNAL_BS, InpForceManualButtons); }
|
|
else if(StringFind(sparam, "_SB_BTN_") > 0) { ExecuteTrade(row_index, SIGNAL_SB, InpForceManualButtons); }
|
|
}
|
|
}
|
|
ChartRedraw();
|
|
}
|
|
|
|
|
|
if(id == CHARTEVENT_OBJECT_ENDEDIT)
|
|
{
|
|
string value = ObjectGetString(0, sparam, OBJPROP_TEXT);
|
|
|
|
// อัปเดตตัวแปรตามช่องที่ถูกแก้ไข
|
|
if(sparam == g_object_prefix + "CTRL_BASKET_EDIT_TP_PERC") g_basket_tp_percent = StringToDouble(value);
|
|
if(sparam == g_object_prefix + "CTRL_BASKET_EDIT_SL_PERC") g_basket_sl_percent = StringToDouble(value);
|
|
if(sparam == g_object_prefix + "CTRL_BASKET_EDIT_TP_MONEY") g_basket_tp_money = StringToDouble(value);
|
|
if(sparam == g_object_prefix + "CTRL_BASKET_EDIT_SL_MONEY") g_basket_sl_money = StringToDouble(value);
|
|
if(sparam == g_object_prefix + "CTRL_EDIT_FIXED_LOT") g_fixed_lot_size = StringToDouble(value);
|
|
if(sparam == g_object_prefix + "CTRL_EDIT_RISK_PERC") g_risk_percent = StringToDouble(value);
|
|
|
|
ChartRedraw();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert timer function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer()
|
|
{
|
|
if(!TerminalInfoInteger(TERMINAL_CONNECTED) || !TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
|
|
return;
|
|
|
|
if(!UpdatePriceCache())
|
|
return;
|
|
|
|
int task = g_timer_counter % 3;
|
|
|
|
switch(task)
|
|
{
|
|
case 0:
|
|
{
|
|
for(int i = 0; i < TOTAL_PAIRS; i++)
|
|
{
|
|
g_corr_data[i].current_corr = ComputePairCorrelation(i);
|
|
bool newBar = false;
|
|
datetime bt[1];
|
|
if(CopyTime(g_corr_data[i].symbol1, g_current_timeframe, 1, 1, bt) > 0)
|
|
{
|
|
if(g_row_last_hist_bar[i] != bt[0])
|
|
{
|
|
newBar = true;
|
|
g_row_last_hist_bar[i] = bt[0];
|
|
|
|
int hs = ArraySize(g_corr_data[i].history);
|
|
ArrayResize(g_corr_data[i].history, hs + 1);
|
|
g_corr_data[i].history[hs] = g_corr_data[i].current_corr;
|
|
if(ArraySize(g_corr_data[i].history) > CORR_HISTORY_SIZE)
|
|
ArrayRemove(g_corr_data[i].history, 0, 1);
|
|
}
|
|
}
|
|
g_corr_data[i].avg_corr = ArrayMean(g_corr_data[i].history);
|
|
g_corr_data[i].delta_corr = g_corr_data[i].current_corr - g_corr_data[i].avg_corr;
|
|
double p1[], p2[];
|
|
if(GetPricesFromCache(g_corr_data[i].symbol1, p1) && GetPricesFromCache(g_corr_data[i].symbol2, p2)
|
|
&& ArraySize(p1) > 20 && ArraySize(p2) > 20)
|
|
{
|
|
double norm_p1 = (p1[0] / p1[20] - 1) * 10000.0;
|
|
double norm_p2 = (p2[0] / p2[20] - 1) * 10000.0;
|
|
double spread = norm_p1 - norm_p2;
|
|
string sprd_text = StringFormat("%+.2f", spread);
|
|
if(g_last_sprd_text[i] != sprd_text)
|
|
{
|
|
ObjectSetString(0, g_object_prefix+"SPRD_DIV_CELL_"+(string)i+"_TXT", OBJPROP_TEXT, sprd_text);
|
|
g_last_sprd_text[i] = sprd_text;
|
|
}
|
|
}
|
|
UpdateRowConfAndHighlight(i);
|
|
}
|
|
|
|
UpdateDashboardData();
|
|
UpdateSignalPanels();
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
UpdateCorrelationMatrix();
|
|
FindMatrixOpportunities();
|
|
UpdateOpportunityPanel();
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
UpdateInfoPanel();
|
|
UpdateTimePanel();
|
|
UpdateAndManageBasket();
|
|
ManageOpenTrades();
|
|
ManageAutoCloseRowsAndOpp();
|
|
UpdatePairTradeStatus();
|
|
|
|
g_timer_counter++;
|
|
ChartRedraw();
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Incrementally updates the price cache for performance. |
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool UpdatePriceCache()
|
|
{
|
|
for(int i=0; i<ArraySize(g_price_cache); ++i)
|
|
{
|
|
datetime tms[2];
|
|
int got = CopyTime(g_price_cache[i].name, g_current_timeframe, 0, 2, tms);
|
|
if(got <= 0) continue;
|
|
|
|
datetime lastBar = (got>=2) ? tms[1] : tms[0];
|
|
if(g_last_cache_time[i] != lastBar)
|
|
{
|
|
double px[];
|
|
int need = InpLookback;
|
|
int n = CopyClose(g_price_cache[i].name, g_current_timeframe, 0, need, px);
|
|
if(n <= 0) continue;
|
|
|
|
ArraySetAsSeries(px, true);
|
|
ArrayResize(g_price_cache[i].prices, n);
|
|
ArrayCopy(g_price_cache[i].prices, px, 0, 0, n);
|
|
|
|
g_last_cache_time[i] = lastBar;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Updates the three main signal panels with dynamic data. |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateSignalPanels()
|
|
{
|
|
int top_pos_idx = -1;
|
|
int top_neg_idx = -1;
|
|
int top_delta_idx = -1;
|
|
|
|
double max_pos_corr = 0.75;
|
|
double min_neg_corr = -0.75;
|
|
double max_abs_delta = 0.10;
|
|
|
|
for(int i = 0; i < TOTAL_PAIRS; i++)
|
|
{
|
|
if(g_corr_data[i].current_corr > max_pos_corr)
|
|
{
|
|
max_pos_corr = g_corr_data[i].current_corr;
|
|
top_pos_idx = i;
|
|
}
|
|
if(g_corr_data[i].current_corr < min_neg_corr)
|
|
{
|
|
min_neg_corr = g_corr_data[i].current_corr;
|
|
top_neg_idx = i;
|
|
}
|
|
if(MathAbs(g_corr_data[i].delta_corr) > max_abs_delta)
|
|
{
|
|
max_abs_delta = MathAbs(g_corr_data[i].delta_corr);
|
|
top_delta_idx = i;
|
|
}
|
|
}
|
|
|
|
string P_ANLS = g_object_prefix + "ANALYSIS_";
|
|
string P_POS = P_ANLS + "SIG_POS_L1";
|
|
if(top_pos_idx != -1)
|
|
{
|
|
string pair_text = g_corr_data[top_pos_idx].symbol1 + "/" + g_corr_data[top_pos_idx].symbol2;
|
|
string new_text = "1. " + pair_text + ": " + StringFormat("%+.2f", g_corr_data[top_pos_idx].current_corr);
|
|
ObjectSetString(0, P_POS, OBJPROP_TEXT, new_text);
|
|
}
|
|
else
|
|
{
|
|
ObjectSetString(0, P_POS, OBJPROP_TEXT, "1. (None > +0.75)");
|
|
}
|
|
|
|
string P_NEG = P_ANLS + "SIG_NEG_L1";
|
|
if(top_neg_idx != -1)
|
|
{
|
|
string pair_text = g_corr_data[top_neg_idx].symbol1 + "/" + g_corr_data[top_neg_idx].symbol2;
|
|
string new_text = "1. " + pair_text + ": " + StringFormat("%+.2f", g_corr_data[top_neg_idx].current_corr);
|
|
ObjectSetString(0, P_NEG, OBJPROP_TEXT, new_text);
|
|
}
|
|
else
|
|
{
|
|
ObjectSetString(0, P_NEG, OBJPROP_TEXT, "1. (None < -0.75)");
|
|
}
|
|
|
|
string P_DEL = P_ANLS + "SIG_DEL_L1";
|
|
if(top_delta_idx != -1)
|
|
{
|
|
string pair_text = g_corr_data[top_delta_idx].symbol1 + "/" + g_corr_data[top_delta_idx].symbol2;
|
|
string new_text = "1. " + pair_text + ": " + StringFormat("%+.2f", g_corr_data[top_delta_idx].delta_corr);
|
|
ObjectSetString(0, P_DEL, OBJPROP_TEXT, new_text);
|
|
}
|
|
else
|
|
{
|
|
ObjectSetString(0, P_DEL, OBJPROP_TEXT, "1. (None > |0.10|)");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Updates the basket information and manages basket exits. |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateAndManageBasket()
|
|
{
|
|
double total_profit = 0;
|
|
int pairs_counter = 0;
|
|
|
|
bool seen_row[TOTAL_PAIRS]; ArrayInitialize(seen_row, false);
|
|
|
|
CPositionInfo pos;
|
|
for(int i=PositionsTotal()-1; i>=0; --i)
|
|
{
|
|
if(!pos.SelectByIndex(i)) continue;
|
|
if(pos.Magic() != InpMagicNumber) continue;
|
|
|
|
total_profit += pos.Profit() + pos.Swap() + pos.Commission();
|
|
|
|
string cmt = pos.Comment();
|
|
string pref = g_object_prefix + "ROW:";
|
|
if(StringFind(cmt, pref) == 0){
|
|
int idx = (int)StringToInteger(StringSubstr(cmt, (int)StringLen(pref)));
|
|
if(idx>=0 && idx<TOTAL_PAIRS && !seen_row[idx]){
|
|
seen_row[idx]=true;
|
|
pairs_counter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
string P = g_object_prefix + "BASKET_";
|
|
ObjectSetString(0, P + "VAL_PAIRS", OBJPROP_TEXT, (string)pairs_counter);
|
|
ObjectSetString(0, P + "VAL_NET_PL", OBJPROP_TEXT, StringFormat("$%+.2f", total_profit));
|
|
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double pl_percent = (equity>0) ? (total_profit/equity)*100.0 : 0.0;
|
|
ObjectSetString(0, P + "VAL_PL_PERC", OBJPROP_TEXT, StringFormat("%+.2f%%", pl_percent));
|
|
static bool trk_armed = false;
|
|
static double trail_floor = -1e9;
|
|
|
|
if(!g_basket_exit_enabled || PositionsTotal()==0){
|
|
trk_armed=false; trail_floor=-1e9; return;
|
|
}
|
|
|
|
if(g_basket_exit_mode == MODE_PERCENT)
|
|
{
|
|
if(pl_percent >= InpBasketTrailArmPercent){
|
|
if(!trk_armed){ trk_armed=true; trail_floor = pl_percent - InpBasketTrailStepPercent; }
|
|
else { trail_floor = MathMax(trail_floor, pl_percent - InpBasketTrailStepPercent); }
|
|
}
|
|
|
|
if(pl_percent >= g_basket_tp_percent){ Print("Basket TP% hit"); CloseAllBasketTrades(); trk_armed=false; trail_floor=-1e9; return; }
|
|
if(pl_percent <= -g_basket_sl_percent){ Print("Basket SL% hit"); CloseAllBasketTrades(); trk_armed=false; trail_floor=-1e9; return; }
|
|
|
|
if(trk_armed && pl_percent <= trail_floor){
|
|
Print("Basket trailing stop hit (", DoubleToString(trail_floor,2), "%)");
|
|
CloseAllBasketTrades(); trk_armed=false; trail_floor=-1e9; return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(total_profit >= g_basket_tp_money){ Print("Basket TP$ hit"); CloseAllBasketTrades(); return; }
|
|
if(total_profit <= -g_basket_sl_money){ Print("Basket SL$ hit"); CloseAllBasketTrades(); return; }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Closes all trades managed by the EA's magic number. |
|
|
//+------------------------------------------------------------------+
|
|
void CloseAllBasketTrades()
|
|
{
|
|
CPositionInfo pi;
|
|
for(int i=PositionsTotal()-1; i>=0; --i)
|
|
{
|
|
if(pi.SelectByIndex(i) && pi.Magic()==InpMagicNumber)
|
|
trade.PositionClose(pi.Ticket());
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Closes all profitable trades managed by the dashboard. (renamed) |
|
|
//+------------------------------------------------------------------+
|
|
void CloseProfitableBasketTrades()
|
|
{
|
|
Print("Attempting to close all profitable trades...");
|
|
CPositionInfo pi;
|
|
for(int i=PositionsTotal()-1; i>=0; --i)
|
|
{
|
|
if(pi.SelectByIndex(i) && pi.Magic()==InpMagicNumber && (pi.Profit()+pi.Swap()) >= 0)
|
|
trade.PositionClose(pi.Ticket());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manages individual open trades based on exit strategies. |
|
|
//+------------------------------------------------------------------+
|
|
|
|
void ManageOpenTrades()
|
|
{
|
|
CPositionInfo pos_info;
|
|
string comment_prefix = g_object_prefix + "ROW:";
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(!pos_info.SelectByIndex(i)) continue;
|
|
if(pos_info.Magic() != InpMagicNumber) continue;
|
|
|
|
string comment = pos_info.Comment();
|
|
if(StringFind(comment, comment_prefix) != 0) continue;
|
|
|
|
string symbol = pos_info.Symbol();
|
|
ulong ticket = pos_info.Ticket();
|
|
|
|
int row_index = (int)StringToInteger(StringSubstr(comment, StringLen(comment_prefix)));
|
|
if(row_index < 0 || row_index >= TOTAL_PAIRS) continue;
|
|
|
|
if(g_use_time_exit)
|
|
{
|
|
long open_time = pos_info.Time();
|
|
int open_bar_index = iBarShift(symbol, g_current_timeframe, (datetime)open_time);
|
|
if(open_bar_index >= g_max_bars_hold)
|
|
{
|
|
Print("Time-based Exit triggered for ", symbol, " (row ", row_index, ")");
|
|
if(trade.PositionClose(ticket))
|
|
g_last_row_close_bar[row_index] = TimeCurrent();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if(g_use_corr_decay_exit)
|
|
{
|
|
double current_corr = g_corr_data[row_index].current_corr;
|
|
if(MathAbs(current_corr) < g_corr_decay_threshold)
|
|
{
|
|
Print("Correlation Decay Exit triggered for ", symbol, " (row ", row_index, ")");
|
|
if(trade.PositionClose(ticket))
|
|
g_last_row_close_bar[row_index] = TimeCurrent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ManageAutoCloseRowsAndOpp()
|
|
{
|
|
if(InpRowAutoTP_Enable)
|
|
{
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double row_pl[TOTAL_PAIRS]; int row_has[TOTAL_PAIRS];
|
|
ArrayInitialize(row_pl, 0.0); ArrayInitialize(row_has, 0);
|
|
|
|
CPositionInfo pi;
|
|
string prefRow = g_object_prefix + "ROW:";
|
|
for(int i=PositionsTotal()-1;i>=0;--i)
|
|
{
|
|
if(!pi.SelectByIndex(i) || pi.Magic()!=InpMagicNumber) continue;
|
|
string c = pi.Comment();
|
|
if(StringFind(c, prefRow)==0)
|
|
{
|
|
int row = (int)StringToInteger(StringSubstr(c, (int)StringLen(prefRow)));
|
|
if(row>=0 && row<TOTAL_PAIRS)
|
|
{
|
|
row_has[row] = 1;
|
|
row_pl[row] += pi.Profit() + pi.Swap() + pi.Commission();
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int r=0;r<TOTAL_PAIRS;r++)
|
|
{
|
|
if(!row_has[r]) continue;
|
|
bool hit=false;
|
|
if(InpRowTPMode==ROW_TP_MODE_MONEY)
|
|
hit = (row_pl[r] >= InpRowTP_Money);
|
|
else
|
|
{
|
|
double perc = (equity>0? (row_pl[r]/equity)*100.0 : 0.0);
|
|
hit = (perc >= InpRowTP_Percent);
|
|
}
|
|
if(hit)
|
|
{
|
|
PrintFormat("[OracleDash] Row %d TP hit -> closing (P/L=%.2f)", r, row_pl[r]);
|
|
ClosePairTrades(r);
|
|
g_last_row_close_bar[r] = TimeCurrent();
|
|
}
|
|
}
|
|
}
|
|
if(InpOppAutoTP_Enable)
|
|
{
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
string keys[]; double vals[];
|
|
CPositionInfo pi;
|
|
string prefOpp = g_object_prefix + "MATRIX:";
|
|
|
|
for(int i=PositionsTotal()-1;i>=0;--i)
|
|
{
|
|
if(!pi.SelectByIndex(i) || pi.Magic()!=InpMagicNumber) continue;
|
|
string c = pi.Comment();
|
|
if(StringFind(c, prefOpp)==0)
|
|
{
|
|
string sym = StringSubstr(c, (int)StringLen(prefOpp));
|
|
if(sym=="") continue;
|
|
|
|
int idx=-1;
|
|
for(int k=0;k<ArraySize(keys);k++) if(keys[k]==sym){ idx=k; break; }
|
|
if(idx<0){ idx=ArraySize(keys); ArrayResize(keys,idx+1); ArrayResize(vals,idx+1); keys[idx]=sym; vals[idx]=0.0; }
|
|
vals[idx] += pi.Profit() + pi.Swap() + pi.Commission();
|
|
}
|
|
}
|
|
for(int k=0;k<ArraySize(keys);k++)
|
|
{
|
|
bool hit=false;
|
|
if(InpOppTPMode==OPP_TP_MODE_MONEY)
|
|
hit = (vals[k] >= InpOppTP_Money);
|
|
else
|
|
{
|
|
double perc = (equity>0? (vals[k]/equity)*100.0 : 0.0);
|
|
hit = (perc >= InpOppTP_Percent);
|
|
}
|
|
|
|
if(hit)
|
|
{
|
|
PrintFormat("[OracleDash] OPP TP hit on %s -> closing (P/L=%.2f)", keys[k], vals[k]);
|
|
CloseMatrixSymbolTrades(keys[k]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculates the trade lot size based on mode. |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateLotSize(string symbol)
|
|
{
|
|
if(g_lot_mode == LOT_MODE_FIXED)
|
|
{
|
|
return g_fixed_lot_size;
|
|
}
|
|
|
|
if(g_lot_mode == LOT_MODE_AUTO)
|
|
{
|
|
double atr_val_array[];
|
|
int atr_handle = iATR(symbol, g_current_timeframe, InpATR_Period);
|
|
if(atr_handle == INVALID_HANDLE) return g_fixed_lot_size;
|
|
if(CopyBuffer(atr_handle, 0, 0, 2, atr_val_array) < 2) return g_fixed_lot_size;
|
|
|
|
double atr_value = atr_val_array[1];
|
|
double stop_loss_pips = atr_value * InpATR_Multiplier;
|
|
if(stop_loss_pips == 0) return g_fixed_lot_size;
|
|
|
|
double account_equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double risk_amount = account_equity * (g_risk_percent / 100.0);
|
|
|
|
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
if(tick_value == 0 || tick_size == 0) return g_fixed_lot_size;
|
|
|
|
double stop_loss_money_per_lot = (stop_loss_pips / tick_size) * tick_value;
|
|
if(stop_loss_money_per_lot == 0) return g_fixed_lot_size;
|
|
|
|
double lot_size = risk_amount / stop_loss_money_per_lot;
|
|
|
|
double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
lot_size = MathFloor(lot_size / volume_step) * volume_step;
|
|
|
|
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
|
|
return(MathMax(min_lot, MathMin(max_lot, lot_size)));
|
|
}
|
|
|
|
return g_fixed_lot_size;
|
|
}
|
|
|
|
double NormalizePriceToTick(const string sym, const double price){
|
|
const double tick = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
|
|
const int digs = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
|
|
if(tick<=0) return NormalizeDouble(price, digs);
|
|
double steps = MathRound(price / tick);
|
|
return NormalizeDouble(steps * tick, digs);
|
|
}
|
|
|
|
double NormalizeVolumeToStep(const string sym, double vol){
|
|
const double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
|
|
const double vmin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
|
|
const double vmax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
|
|
if(step<=0) return vol;
|
|
vol = MathFloor(vol/step)*step;
|
|
if(vol < vmin) vol = vmin;
|
|
if(vol > vmax) vol = vmax;
|
|
return vol;
|
|
}
|
|
|
|
double SnapToTick(const string sym, const double price, const bool down)
|
|
{
|
|
const double tick = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
|
|
const int digs = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
|
|
if(tick <= 0) return NormalizeDouble(price, digs);
|
|
double steps = price / tick;
|
|
return NormalizeDouble((down ? MathFloor(steps) : MathCeil(steps)) * tick, digs);
|
|
}
|
|
|
|
void EnsureStopsRespectLevels(const string sym, const bool isBuy, double &sl, double &tp)
|
|
{
|
|
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
|
|
const int stops = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL);
|
|
const int freeze = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_FREEZE_LEVEL);
|
|
const double minDist = pt * MathMax(stops, freeze);
|
|
|
|
const double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
|
|
const double bid = SymbolInfoDouble(sym, SYMBOL_BID);
|
|
|
|
if(isBuy){
|
|
if(sl>0 && (ask - sl) < minDist) sl = SnapToTick(sym, ask - minDist, true);
|
|
if(tp>0 && (tp - ask) < minDist) tp = SnapToTick(sym, ask + minDist, false);
|
|
}else{
|
|
if(sl>0 && (sl - bid) < minDist) sl = SnapToTick(sym, bid + minDist, false);
|
|
if(tp>0 && (bid - tp) < minDist) tp = SnapToTick(sym, bid - minDist, true);
|
|
}
|
|
}
|
|
|
|
bool TryOrderSend(MqlTradeRequest &req, MqlTradeResult &res)
|
|
{
|
|
ENUM_ORDER_TYPE_FILLING tries[3] = { ORDER_FILLING_RETURN, ORDER_FILLING_IOC, ORDER_FILLING_FOK };
|
|
int n=3;
|
|
int sym_mode = (int)SymbolInfoInteger(req.symbol, SYMBOL_FILLING_MODE);
|
|
if(sym_mode>=0 && sym_mode<=2){ tries[0]=(ENUM_ORDER_TYPE_FILLING)sym_mode; tries[1]=ORDER_FILLING_IOC; tries[2]=ORDER_FILLING_RETURN; }
|
|
|
|
for(int i=0;i<n;i++){
|
|
req.type_filling = tries[i];
|
|
if(OrderSend(req, res)){
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
bool SendDealRawEx(const string sym, const ENUM_ORDER_TYPE typ, double vol, double sl, double tp,
|
|
const string &cmt, uint &retcodeOut, string &commentOut)
|
|
{
|
|
retcodeOut=0; commentOut="";
|
|
if(sym=="" || (typ!=ORDER_TYPE_BUY && typ!=ORDER_TYPE_SELL)) return false;
|
|
if(!EnsureSymbolSelected(sym)) return false;
|
|
|
|
vol = NormalizeVolumeToStep(sym, vol);
|
|
if(vol<=0) return false;
|
|
|
|
double px = (typ==ORDER_TYPE_BUY)? SymbolInfoDouble(sym, SYMBOL_ASK): SymbolInfoDouble(sym, SYMBOL_BID);
|
|
px = NormalizePriceToTick(sym, px);
|
|
if(sl>0 || tp>0) EnsureStopsRespectLevels(sym, (typ==ORDER_TYPE_BUY), sl, tp);
|
|
|
|
MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req);
|
|
req.action = TRADE_ACTION_DEAL;
|
|
req.type = typ;
|
|
req.symbol = sym;
|
|
req.volume = vol;
|
|
req.price = px;
|
|
req.sl = (sl>0? NormalizePriceToTick(sym, sl):0.0);
|
|
req.tp = (tp>0? NormalizePriceToTick(sym, tp):0.0);
|
|
req.magic = InpMagicNumber;
|
|
req.type_time = ORDER_TIME_GTC;
|
|
req.deviation = 300;
|
|
req.type_filling = (ENUM_ORDER_TYPE_FILLING)SymbolInfoInteger(sym, SYMBOL_FILLING_MODE);
|
|
req.comment = cmt;
|
|
|
|
bool ok = TryOrderSend(req, res);
|
|
retcodeOut = res.retcode;
|
|
commentOut = res.comment;
|
|
return ok;
|
|
}
|
|
|
|
bool SendDeal(const string sym, const ENUM_ORDER_TYPE typ, double vol, const string &trade_comment)
|
|
{
|
|
if(sym=="" || (typ!=ORDER_TYPE_BUY && typ!=ORDER_TYPE_SELL)) return false;
|
|
if(!EnsureSymbolSelected(sym)) { Print("Symbol not available: ", sym); return false; }
|
|
if(!PassesPreTradeFilters(sym)) { Print("PreFilter blocked trade on ", sym); return false; }
|
|
|
|
vol = NormalizeVolumeToStep(sym, vol);
|
|
if(vol <= 0){ Print("Volume rejected after normalize: ", vol); return false; }
|
|
double sl=0.0, tp=0.0;
|
|
if(g_use_sl_tp){
|
|
BuildSLTP(sym, (typ==ORDER_TYPE_BUY), InpATR_Multiplier, sl, tp, g_rr);
|
|
EnsureStopsRespectLevels(sym, (typ==ORDER_TYPE_BUY), sl, tp);
|
|
if(sl>0) sl = NormalizePriceToTick(sym, sl);
|
|
if(tp>0) tp = NormalizePriceToTick(sym, tp);
|
|
}
|
|
|
|
const double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
|
|
const double bid = SymbolInfoDouble(sym, SYMBOL_BID);
|
|
double px = (typ==ORDER_TYPE_BUY) ? ask : bid;
|
|
px = NormalizePriceToTick(sym, px);
|
|
|
|
MqlTradeRequest req; MqlTradeResult res;
|
|
ZeroMemory(req);
|
|
req.action = TRADE_ACTION_DEAL;
|
|
req.type = typ;
|
|
req.symbol = sym;
|
|
req.volume = vol;
|
|
req.price = px;
|
|
req.sl = sl;
|
|
req.tp = tp;
|
|
req.magic = InpMagicNumber;
|
|
req.deviation = 300;
|
|
req.type_time = ORDER_TIME_GTC;
|
|
req.comment = trade_comment;
|
|
|
|
if(!TryOrderSend(req, res)){
|
|
Print("OrderSend failed on ", sym, " retcode=", res.retcode, " comment=", res.comment);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Executes a trade for a specific pair index. |
|
|
//+------------------------------------------------------------------+
|
|
void ExecuteTrade(int index, ENUM_SIGNAL_TYPE signal_type, bool allow_force)
|
|
{
|
|
if(signal_type == SIGNAL_NONE) return;
|
|
if(RowOpenCount(index) >= InpMaxPositionsPerRow){
|
|
NotifyUser(StringFormat("Skip row %d: reached max %d positions.", index, InpMaxPositionsPerRow));
|
|
return;
|
|
}
|
|
|
|
if(InRowCooldown(index)) { NotifyUser(StringFormat("Skip row %d: cooldown.", index)); return; }
|
|
|
|
string s1 = g_corr_data[index].symbol1;
|
|
string s2 = g_corr_data[index].symbol2;
|
|
|
|
// ทิศทางต่อขา
|
|
ENUM_ORDER_TYPE t1, t2;
|
|
if(signal_type==SIGNAL_BB){ t1=ORDER_TYPE_BUY; t2=ORDER_TYPE_BUY; }
|
|
else if(signal_type==SIGNAL_SS){ t1=ORDER_TYPE_SELL; t2=ORDER_TYPE_SELL; }
|
|
else if(signal_type==SIGNAL_BS){ t1=ORDER_TYPE_BUY; t2=ORDER_TYPE_SELL; }
|
|
else if(signal_type==SIGNAL_SB){ t1=ORDER_TYPE_SELL; t2=ORDER_TYPE_BUY; }
|
|
else return;
|
|
double lot1 = CalculateLotSize(s1);
|
|
double lot2 = CalculateLotSize(s2);
|
|
AdjustLotsRiskParity(s1, s2, lot1, lot2);
|
|
|
|
double sl1=0,tp1=0, sl2=0,tp2=0;
|
|
if(g_use_sl_tp){
|
|
BuildSLTP(s1, (t1==ORDER_TYPE_BUY), InpATR_Multiplier, sl1, tp1, g_rr);
|
|
BuildSLTP(s2, (t2==ORDER_TYPE_BUY), InpATR_Multiplier, sl2, tp2, g_rr);
|
|
EnsureStopsRespectLevels(s1, (t1==ORDER_TYPE_BUY), sl1, tp1);
|
|
EnsureStopsRespectLevels(s2, (t2==ORDER_TYPE_BUY), sl2, tp2);
|
|
}
|
|
|
|
bool pf1 = PassesPreTradeFilters(s1);
|
|
bool pf2 = PassesPreTradeFilters(s2);
|
|
|
|
string reasonA="", reasonB=""; uint rcA=0, rcB=0;
|
|
bool pre1 = PreflightDeal(s1, t1, lot1, sl1, tp1, reasonA, rcA);
|
|
bool pre2 = PreflightDeal(s2, t2, lot2, sl2, tp2, reasonB, rcB);
|
|
|
|
double pv1 = PipValuePerLot(s1), pv2 = PipValuePerLot(s2);
|
|
double rA = lot1 * pv1, rB = lot2 * pv2;
|
|
double diffpct = (MathMax(rA,rB)>0 ? 100.0 * MathAbs(rA-rB) / MathMax(rA,rB) : 0.0);
|
|
|
|
bool any_fail = (!pf1 || !pf2 || !pre1 || !pre2);
|
|
bool high_mismatch = (diffpct > InpRiskMismatchWarnPct);
|
|
|
|
if(any_fail || high_mismatch)
|
|
{
|
|
if(!allow_force){
|
|
string msg = StringFormat("Row %d blocked. pf:%s/%s pre:%s/%s Δrisk≈%.1f%%",
|
|
index, (pf1?"OK":"FAIL"), (pf2?"OK":"FAIL"),
|
|
(pre1?"OK":"FAIL"), (pre2?"OK":"FAIL"),
|
|
diffpct);
|
|
NotifyUser(msg);
|
|
return;
|
|
}
|
|
if(!g_silent_force)
|
|
{
|
|
string warn = "Force pair entry?\n\n";
|
|
if(!pf1) warn += s1 + " prefilter: FAIL\n";
|
|
if(!pre1) warn += s1 + " precheck: " + reasonA + "\n";
|
|
if(!pf2) warn += s2 + " prefilter: FAIL\n";
|
|
if(!pre2) warn += s2 + " precheck: " + reasonB + "\n";
|
|
if(high_mismatch) warn += StringFormat("Risk mismatch ~%.1f%%\n", diffpct);
|
|
warn += "\nกด OK เพื่อบังคับเข้าออเดอร์ทั้งสองขา หรือ Cancel เพื่อยกเลิก";
|
|
|
|
if(!ConfirmForce("OracleDash — Force Entry", warn)) return;
|
|
}
|
|
}
|
|
|
|
string tag = g_object_prefix + "ROW:" + (string)index;
|
|
if(g_exec_ctx == EXEC_FORCE_AUTO) tag += " | MODE:FORCE_AUTO";
|
|
else if(g_exec_ctx == EXEC_AUTO) tag += " | MODE:AUTO";
|
|
|
|
uint s_rcA=0, s_rcB=0; string s_cmA="", s_cmB="";
|
|
|
|
bool okA = SendDealRawEx(s1, t1, lot1, sl1, tp1, tag, s_rcA, s_cmA);
|
|
if(!okA){
|
|
NotifyUser(StringFormat("Row %d %s %s SEND FAIL (%u): %s",
|
|
index, s1, LegLabel(t1), s_rcA, s_cmA));
|
|
return;
|
|
}
|
|
NotifyUser(StringFormat("Row %d %s %s OK (%u): %s vol=%.2f",
|
|
index, s1, LegLabel(t1), s_rcA, s_cmA, lot1));
|
|
|
|
bool okB = SendDealRawEx(s2, t2, lot2, sl2, tp2, tag, s_rcB, s_cmB);
|
|
if(!okB){
|
|
NotifyUser(StringFormat("Row %d %s %s SEND FAIL (%u): %s",
|
|
index, s2, LegLabel(t2), s_rcB, s_cmB));
|
|
CPositionInfo pi; bool closed=false;
|
|
string pref = g_object_prefix + "ROW:" + (string)index;
|
|
for(int i=PositionsTotal()-1;i>=0;--i)
|
|
if(pi.SelectByIndex(i) && pi.Magic()==InpMagicNumber &&
|
|
pi.Symbol()==s1 && StringFind(pi.Comment(), pref)==0)
|
|
{ closed = trade.PositionClose(pi.Ticket()); break; }
|
|
|
|
NotifyUser(StringFormat("Row %d compensate legA %s %s -> %s",
|
|
index, s1, LegLabel(t1), (closed?"CLOSED":"FAILED")));
|
|
return;
|
|
}
|
|
NotifyUser(StringFormat("Row %d %s %s OK (%u): %s vol=%.2f",
|
|
index, s2, LegLabel(t2), s_rcB, s_cmB, lot2));
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Executes a single matrix trade. |
|
|
//+------------------------------------------------------------------+
|
|
void ExecuteMatrixTrade(string symbol, ENUM_SIGNAL_TYPE trade_action)
|
|
{
|
|
if (trade_action == SIGNAL_NONE || symbol == "") return;
|
|
if(StringSubstr(symbol,0,3)=="XAU")
|
|
{
|
|
ENUM_SIGNAL_TYPE s;
|
|
if(!SingleSymbolDirection(symbol, s) || s != trade_action)
|
|
{
|
|
Print("XAU filter disagrees. Skip ", symbol);
|
|
return;
|
|
}
|
|
}
|
|
|
|
double lot = CalculateLotSize(symbol);
|
|
string cmt = g_object_prefix + "MATRIX:" + symbol;
|
|
|
|
if(trade_action == SIGNAL_BS) SendDeal(symbol, ORDER_TYPE_BUY, lot, cmt);
|
|
else if(trade_action == SIGNAL_SB) SendDeal(symbol, ORDER_TYPE_SELL, lot, cmt);
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculates the signal confidence. |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateSignalConfidence(double correlation, double delta_corr)
|
|
{
|
|
double confidence = 0;
|
|
double corr_abs = MathAbs(correlation);
|
|
if(corr_abs >= 0.7)
|
|
{
|
|
confidence += ((corr_abs - 0.7) / 0.3) * 50.0;
|
|
}
|
|
double delta_abs = MathAbs(delta_corr);
|
|
if(delta_abs > 0.05)
|
|
{
|
|
double delta_capped = MathMin(delta_abs, 0.25);
|
|
confidence += ((delta_capped - 0.05) / 0.20) * 50.0;
|
|
}
|
|
return MathMin(confidence, 100.0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Checks for trading signals based on correlation + HA (+ Zscore). |
|
|
//+------------------------------------------------------------------+
|
|
SignalInfo CheckTradingSignals(int index)
|
|
{
|
|
SignalInfo out;
|
|
if(index < 0 || index >= TOTAL_PAIRS) return out;
|
|
|
|
string s1 = g_corr_data[index].symbol1;
|
|
string s2 = g_corr_data[index].symbol2;
|
|
double corr = g_corr_data[index].current_corr;
|
|
double avg = g_corr_data[index].avg_corr;
|
|
double dCorr = g_corr_data[index].delta_corr;
|
|
double z_hist = ZscoreArray(g_corr_data[index].history);
|
|
int ha1 = g_ha_handles_main[index][0];
|
|
int ha2 = g_ha_handles_main[index][1];
|
|
double o1[], c1[], o2[], c2[];
|
|
bool haveHA = !(ha1==INVALID_HANDLE || ha2==INVALID_HANDLE ||
|
|
CopyBuffer(ha1,0,0,1,o1)<1 || CopyBuffer(ha1,3,0,1,c1)<1 ||
|
|
CopyBuffer(ha2,0,0,1,o2)<1 || CopyBuffer(ha2,3,0,1,c2)<1);
|
|
|
|
bool s1_bull=false, s1_bear=false, s2_bull=false, s2_bear=false;
|
|
if(haveHA){ s1_bull = (c1[0] > o1[0]); s1_bear = (c1[0] < o1[0]);
|
|
s2_bull = (c2[0] > o2[0]); s2_bear = (c2[0] < o2[0]); }
|
|
|
|
bool trend_ok = true;
|
|
double a1[], a2[];
|
|
if(g_adx_handles_main[index][0]!=INVALID_HANDLE &&
|
|
g_adx_handles_main[index][1]!=INVALID_HANDLE &&
|
|
CopyBuffer(g_adx_handles_main[index][0],2,0,2,a1)==2 &&
|
|
CopyBuffer(g_adx_handles_main[index][1],2,0,2,a2)==2)
|
|
{
|
|
double adx_val = (a1[1]+a2[1])*0.5;
|
|
trend_ok = (adx_val >= InpADX_TrendLevel);
|
|
}
|
|
|
|
double baseConf = CalculateSignalConfidence(corr, dCorr);
|
|
if(!haveHA) baseConf *= 0.85;
|
|
|
|
bool ok_bb=true, ok_ss=true, ok_bs=true, ok_sb=true;
|
|
if(g_use_mtf_filter)
|
|
{
|
|
int ha1c = g_ha_handles_conf[index][0];
|
|
int ha2c = g_ha_handles_conf[index][1];
|
|
double oc1[], cc1[], oc2[], cc2[];
|
|
bool haveMTF = !(ha1c==INVALID_HANDLE || ha2c==INVALID_HANDLE ||
|
|
CopyBuffer(ha1c,0,0,1,oc1)<1 || CopyBuffer(ha1c,3,0,1,cc1)<1 ||
|
|
CopyBuffer(ha2c,0,0,1,oc2)<1 || CopyBuffer(ha2c,3,0,1,cc2)<1);
|
|
|
|
if(haveMTF){
|
|
bool s1c_bull = (cc1[0] > oc1[0]);
|
|
bool s1c_bear = (cc1[0] < oc1[0]);
|
|
bool s2c_bull = (cc2[0] > oc2[0]);
|
|
bool s2c_bear = (cc2[0] < oc2[0]);
|
|
|
|
ok_bb = (s1c_bull && s2c_bull);
|
|
ok_ss = (s1c_bear && s2c_bear);
|
|
ok_bs = (s1c_bull && s2c_bear);
|
|
ok_sb = (s1c_bear && s2c_bull);
|
|
}else{
|
|
baseConf *= 0.90;
|
|
}
|
|
}
|
|
|
|
const double POS_THR = InpCorrPosThreshold;
|
|
const double NEG_THR = InpCorrNegThreshold;
|
|
const double Z_ENTER = InpZSpikeEnter;
|
|
const double Z_EXIT = InpZSpikeExit;
|
|
double scBB = baseConf;
|
|
{
|
|
double align = (haveHA && s1_bull && s2_bull ? 1.0 : 0.6);
|
|
double mtf = (ok_bb ? 1.0 : 0.7);
|
|
double sign = (corr >= POS_THR ? 1.0 : (avg >= POS_THR ? 0.8 : 0.5));
|
|
bool bb_trend = (trend_ok && haveHA && s1_bull && s2_bull && (!g_use_mtf_filter || ok_bb));
|
|
double zok = (MathAbs(z_hist) <= Z_EXIT || bb_trend ? 1.0 : 0.85);
|
|
double adx = (trend_ok ? 1.0 : 0.75);
|
|
scBB *= Clamp01(align*mtf*sign*zok*adx);
|
|
}
|
|
|
|
double scSS = baseConf;
|
|
{
|
|
double align = (haveHA && s1_bear && s2_bear ? 1.0 : 0.6);
|
|
double mtf = (ok_ss ? 1.0 : 0.7);
|
|
double sign = (corr >= POS_THR ? 1.0 : (avg >= POS_THR ? 0.8 : 0.5));
|
|
bool ss_trend = (trend_ok && haveHA && s1_bear && s2_bear && (!g_use_mtf_filter || ok_ss));
|
|
double zok = (MathAbs(z_hist) <= Z_EXIT || ss_trend ? 1.0 : 0.85);
|
|
double adx = (trend_ok ? 1.0 : 0.75);
|
|
scSS *= Clamp01(align*mtf*sign*zok*adx);
|
|
}
|
|
|
|
double scBS = baseConf;
|
|
{
|
|
double align = (haveHA && s1_bull && s2_bear ? 1.0 : 0.6);
|
|
double mtf = (ok_bs ? 1.0 : 0.7);
|
|
double sign = (corr <= NEG_THR ? 1.0 : (avg <= NEG_THR ? 0.8 : 0.5));
|
|
double zok = (MathAbs(z_hist) >= Z_ENTER ? 1.0 : 0.9);
|
|
double adx = (trend_ok ? 0.95 : 0.90);
|
|
scBS *= Clamp01(align*mtf*sign*zok*adx);
|
|
}
|
|
|
|
double scSB = baseConf;
|
|
{
|
|
double align = (haveHA && s1_bear && s2_bull ? 1.0 : 0.6);
|
|
double mtf = (ok_sb ? 1.0 : 0.7);
|
|
double sign = (corr <= NEG_THR ? 1.0 : (avg <= NEG_THR ? 0.8 : 0.5));
|
|
double zok = (MathAbs(z_hist) >= Z_ENTER ? 1.0 : 0.9);
|
|
double adx = (trend_ok ? 0.95 : 0.90);
|
|
scSB *= Clamp01(align*mtf*sign*zok*adx);
|
|
}
|
|
double z_spread=0.0; ENUM_SIGNAL_TYPE spr_sig=SIGNAL_NONE;
|
|
if(PairSignalBySpread(index, z_spread, spr_sig))
|
|
{
|
|
double opp_mult = (corr >= POS_THR ? 1.10 : 1.25);
|
|
if(spr_sig==SIGNAL_BS) scBS *= opp_mult;
|
|
if(spr_sig==SIGNAL_SB) scSB *= opp_mult;
|
|
|
|
if(MathAbs(z_spread) >= (Z_ENTER + 0.4))
|
|
{
|
|
double add = (corr >= POS_THR ? 4.0 : 10.0);
|
|
if(spr_sig==SIGNAL_BS) scBS += add;
|
|
if(spr_sig==SIGNAL_SB) scSB += add;
|
|
}
|
|
}
|
|
bool pos_corr_strong = (corr >= POS_THR);
|
|
bool neg_corr_strong = (corr <= NEG_THR);
|
|
|
|
if(pos_corr_strong)
|
|
{
|
|
bool bb_trend = (haveHA && s1_bull && s2_bull && (!g_use_mtf_filter || ok_bb) && trend_ok);
|
|
bool ss_trend = (haveHA && s1_bear && s2_bear && (!g_use_mtf_filter || ok_ss) && trend_ok);
|
|
|
|
if(bb_trend) scBB = MathMin(100.0, scBB*1.20 + 6.0);
|
|
if(ss_trend) scSS = MathMin(100.0, scSS*1.20 + 6.0);
|
|
scBS *= 0.75;
|
|
scSB *= 0.75;
|
|
}
|
|
else if(neg_corr_strong)
|
|
{
|
|
scBB *= 0.75;
|
|
scSS *= 0.75;
|
|
}
|
|
if(!PassesPreTradeFilters(s1) || !PassesPreTradeFilters(s2))
|
|
{ scBB *= 0.85; scSS *= 0.85; scBS *= 0.85; scSB *= 0.85; }
|
|
scBB = MathMin(100.0, MathMax(0.0, scBB));
|
|
scSS = MathMin(100.0, MathMax(0.0, scSS));
|
|
scBS = MathMin(100.0, MathMax(0.0, scBS));
|
|
scSB = MathMin(100.0, MathMax(0.0, scSB));
|
|
out.type = SIGNAL_BB;
|
|
out.confidence = scBB;
|
|
if(scSS > out.confidence){ out.type = SIGNAL_SS; out.confidence = scSS; }
|
|
if(scBS > out.confidence){ out.type = SIGNAL_BS; out.confidence = scBS; }
|
|
if(scSB > out.confidence){ out.type = SIGNAL_SB; out.confidence = scSB; }
|
|
out.confidence = AdjustConfidenceByLogic(out.confidence);
|
|
return out;
|
|
}
|
|
|
|
|
|
void ComputeMatrixStrength(double &strength[])
|
|
{
|
|
int n = ArraySize(g_matrix_symbols);
|
|
ArrayResize(strength, n);
|
|
for(int i=0;i<n;i++){
|
|
double s=0; int cnt=0;
|
|
for(int j=0;j<n;j++){ if(i==j) continue; s += g_matrix_corr[i][j]; cnt++; }
|
|
strength[i] = (cnt>0) ? (s/cnt) : 0.0;
|
|
}
|
|
}
|
|
|
|
bool LegsForCurrencies(const string a, const string b, string &legA, string &legB)
|
|
{
|
|
legA = GetSymbolForCurrencies(a, "USD");
|
|
legB = GetSymbolForCurrencies(b, "USD");
|
|
if(legA=="" || legB==""){
|
|
if(legA=="") legA = GetSymbolForCurrencies(a, "EUR");
|
|
if(legB=="") legB = GetSymbolForCurrencies(b, "EUR");
|
|
}
|
|
return (legA!="" && legB!="");
|
|
}
|
|
|
|
bool ZSpreadByLegs(const string ccy1, const string ccy2, double &z, ENUM_SIGNAL_TYPE &sig)
|
|
{
|
|
z=0.0; sig=SIGNAL_NONE;
|
|
string l1,l2; if(!LegsForCurrencies(ccy1, ccy2, l1, l2)) return false;
|
|
|
|
double p1[], p2[]; if(!GetPricesFromCache(l1,p1) || !GetPricesFromCache(l2,p2)) return false;
|
|
int n = MathMin(ArraySize(p1), ArraySize(p2)); if(n < 60) return false;
|
|
|
|
double L1[], L2[]; ArrayResize(L1,n); ArrayResize(L2,n);
|
|
for(int i=0;i<n;i++){ L1[i]=MathLog(p1[i]); L2[i]=MathLog(p2[i]); }
|
|
|
|
double beta; if(!HedgeBeta(L1,L2,n,beta)) return false;
|
|
|
|
double spr[]; ArrayResize(spr,n);
|
|
for(int i=0;i<n;i++) spr[i] = L1[i] - beta*L2[i];
|
|
|
|
double z0 = ZScoreSeriesLatest(spr);
|
|
z = z0;
|
|
|
|
if(z0 >= 1.20) sig = SIGNAL_SB;
|
|
else if(z0 <= -1.20) sig = SIGNAL_BS;
|
|
return true;
|
|
}
|
|
|
|
void FindMatrixOpportunities()
|
|
{
|
|
for(int i=0;i<ArraySize(g_opportunities);i++){
|
|
g_opportunities[i].symbol = "";
|
|
g_opportunities[i].style = "";
|
|
g_opportunities[i].trade_action = SIGNAL_NONE;
|
|
g_opportunities[i].correlation = 0.0;
|
|
g_opportunities[i].confidence = 0.0;
|
|
}
|
|
|
|
OpportunityInfo found[];
|
|
int found_count = 0;
|
|
int n = ArraySize(g_matrix_symbols);
|
|
|
|
|
|
const double MR_minAbsCorr = 0.60;
|
|
const double MR_minZ = 1.20;
|
|
|
|
const double SW_minAbsCorr = 0.45;
|
|
const double SW_minGap = 0.15;
|
|
|
|
const double SC_minAbsCorr = 0.30;
|
|
const double SC_minGap = 0.08;
|
|
const double SC_maxSpread = 25.0; //
|
|
const bool SC_needSess = true;
|
|
|
|
double strength[];
|
|
ComputeMatrixStrength(strength);
|
|
|
|
bool wantMR = (InpOppMode==OPP_MODE_MEANREVERT || InpOppMode==OPP_MODE_AUTO);
|
|
bool wantSW = (InpOppMode==OPP_MODE_SWING || InpOppMode==OPP_MODE_AUTO);
|
|
bool wantSC = (InpOppMode==OPP_MODE_SCALP || InpOppMode==OPP_MODE_AUTO);
|
|
|
|
for(int r=0; r<n; r++)
|
|
{
|
|
for(int c=r+1; c<n; c++)
|
|
{
|
|
double corr = g_matrix_corr[r][c];
|
|
string c1 = g_matrix_symbols[r];
|
|
string c2 = g_matrix_symbols[c];
|
|
|
|
string symbol = GetSymbolForCurrencies(c1, c2);
|
|
if(symbol=="") continue;
|
|
|
|
if(wantMR && MathAbs(corr) >= MR_minAbsCorr)
|
|
{
|
|
double z; ENUM_SIGNAL_TYPE sig = SIGNAL_NONE;
|
|
if(ZSpreadByLegs(c1, c2, z, sig) && sig!=SIGNAL_NONE && MathAbs(z) >= MR_minZ)
|
|
AddOpp(found, found_count, symbol, corr, "MR", sig);
|
|
}
|
|
|
|
if(wantSW && MathAbs(corr) >= SW_minAbsCorr)
|
|
{
|
|
double gap = strength[r] - strength[c];
|
|
if(MathAbs(gap) >= SW_minGap)
|
|
{
|
|
ENUM_SIGNAL_TYPE sig = (gap>0 ? SIGNAL_BS : SIGNAL_SB);
|
|
AddOpp(found, found_count, symbol, corr, "SWING", sig);
|
|
}
|
|
}
|
|
|
|
if(wantSC && MathAbs(corr) >= SC_minAbsCorr)
|
|
{
|
|
double gap = strength[r] - strength[c];
|
|
bool ok_gap = (MathAbs(gap) >= SC_minGap);
|
|
bool ok_sp = (GetSpreadPts(symbol) <= SC_maxSpread);
|
|
bool ok_sess = (!SC_needSess || IsMajorSessionOpen());
|
|
if(ok_gap && ok_sp && ok_sess)
|
|
{
|
|
ENUM_SIGNAL_TYPE sig = (gap>0 ? SIGNAL_BS : SIGNAL_SB);
|
|
AddOpp(found, found_count, symbol, corr, "SCALP", sig);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(int i=0;i<found_count-1;i++)
|
|
for(int j=i+1;j<found_count;j++)
|
|
if(found[j].confidence > found[i].confidence)
|
|
{ OpportunityInfo t=found[i]; found[i]=found[j]; found[j]=t; }
|
|
|
|
int k = MathMin(5, found_count);
|
|
for(int i=0;i<k;i++) g_opportunities[i] = found[i];
|
|
}
|
|
|
|
|
|
void AddOpp(OpportunityInfo &arr[], int &count,
|
|
const string sym, const double corr,
|
|
const string style, const ENUM_SIGNAL_TYPE sig)
|
|
{
|
|
ArrayResize(arr, count+1);
|
|
arr[count].symbol = sym;
|
|
arr[count].correlation = corr;
|
|
arr[count].style = style;
|
|
arr[count].trade_action = sig;
|
|
|
|
double conf = CalcOpportunityConfidence(sym, corr, sig);
|
|
if(style == "SCALP") conf *= 0.85;
|
|
if(conf > 100.0) conf = 100.0;
|
|
if(conf < 0.0) conf = 0.0;
|
|
|
|
arr[count].confidence = conf;
|
|
count++;
|
|
}
|
|
|
|
bool MakeReturns(const double &px[], int n_in, double &ret[])
|
|
{
|
|
int n = MathMin(n_in, ArraySize(px));
|
|
if(n < 2) return false;
|
|
ArrayResize(ret, n-1);
|
|
for(int i=1; i<n; ++i)
|
|
ret[i-1] = MathLog(px[i-1]) - MathLog(px[i]);
|
|
return true;
|
|
}
|
|
|
|
double CorrOfReturns(const double &px1[], const double &px2[], int count)
|
|
{
|
|
double r1[], r2[];
|
|
if(!MakeReturns(px1, count, r1) || !MakeReturns(px2, count, r2)) return 0.0;
|
|
int m = MathMin(ArraySize(r1), ArraySize(r2));
|
|
if(m < 5) return 0.0;
|
|
return CalculatePearsonCorrelation(r1, r2, m);
|
|
}
|
|
|
|
double ComputePairCorrelation(const int pair_index)
|
|
{
|
|
if(pair_index < 0 || pair_index >= TOTAL_PAIRS) return 0.0;
|
|
double p1[], p2[];
|
|
if(!GetPricesFromCache(g_corr_data[pair_index].symbol1, p1)) return 0.0;
|
|
if(!GetPricesFromCache(g_corr_data[pair_index].symbol2, p2)) return 0.0;
|
|
return CorrOfReturns(p1, p2, InpLookback);
|
|
}
|
|
|
|
void UpdateCorrelationMatrix()
|
|
{
|
|
int num_currencies = ArraySize(g_matrix_symbols);
|
|
double pA[], pB[], u1[], u2[];
|
|
for(int r = 0; r < num_currencies; r++)
|
|
{
|
|
for(int c = 0; c < num_currencies; c++)
|
|
{
|
|
if(r == c) { g_matrix_corr[r][c] = 1.0; }
|
|
else
|
|
{
|
|
string leg1 = GetSymbolForCurrencies(g_matrix_symbols[r], "USD");
|
|
string leg2 = GetSymbolForCurrencies(g_matrix_symbols[c], "USD");
|
|
|
|
if(leg1 == "") leg1 = GetSymbolForCurrencies(g_matrix_symbols[r], "EUR");
|
|
if(leg2 == "") leg2 = GetSymbolForCurrencies(g_matrix_symbols[c], "EUR");
|
|
|
|
if(leg1 != "" && leg2 != "" &&
|
|
GetPricesFromCache(leg1, pA) && GetPricesFromCache(leg2, pB))
|
|
{
|
|
double corr = CorrOfReturns(pA, pB, InpLookback);
|
|
|
|
if(StringSubstr(leg1, 0, 3) != g_matrix_symbols[r]) corr *= -1;
|
|
if(StringSubstr(leg2, 0, 3) != g_matrix_symbols[c]) corr *= -1;
|
|
|
|
g_matrix_corr[r][c] = corr;
|
|
}
|
|
else g_matrix_corr[r][c] = 0.0;
|
|
}
|
|
|
|
string name = g_object_prefix + "MATRIX_CELL_" + (string)r + "_" + (string)c;
|
|
double cell_corr = g_matrix_corr[r][c];
|
|
|
|
color bg_color;
|
|
|
|
if(r == c)
|
|
{
|
|
bg_color = c_header_bg;
|
|
}
|
|
else
|
|
{
|
|
if(cell_corr >= 0.70) bg_color = C'0,170,0';
|
|
else if(cell_corr >= 0.30) bg_color = C'0,100,0';
|
|
else if(cell_corr <= -0.70) bg_color = C'170,0,0';
|
|
else if(cell_corr <= -0.30) bg_color = C'100,0,0';
|
|
else bg_color = c_bg_dark;
|
|
}
|
|
|
|
ObjectSetString(0, name + "_TXT", OBJPROP_TEXT, StringFormat("%+.2f", cell_corr));
|
|
ObjectSetInteger(0, name + "_BG", OBJPROP_BGCOLOR, bg_color);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Updates the profit and order count for each pair row. |
|
|
//+------------------------------------------------------------------+
|
|
void UpdatePairTradeStatus()
|
|
{
|
|
double row_profit[TOTAL_PAIRS];
|
|
int row_orders[TOTAL_PAIRS];
|
|
ArrayInitialize(row_profit, 0.0);
|
|
ArrayInitialize(row_orders, 0);
|
|
|
|
CPositionInfo pos_info;
|
|
string comment_prefix = g_object_prefix + "ROW:";
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(pos_info.SelectByIndex(i))
|
|
{
|
|
if(pos_info.Magic() == InpMagicNumber)
|
|
{
|
|
string comment = pos_info.Comment();
|
|
if(StringFind(comment, comment_prefix) == 0)
|
|
{
|
|
string index_str = StringSubstr(comment, StringLen(comment_prefix));
|
|
int row_index = (int)StringToInteger(index_str);
|
|
|
|
if(row_index >= 0 && row_index < TOTAL_PAIRS)
|
|
{
|
|
row_profit[row_index] += pos_info.Profit() + pos_info.Swap() + pos_info.Commission();
|
|
row_orders[row_index]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < TOTAL_PAIRS; i++)
|
|
{
|
|
string profit_text;
|
|
color profit_color;
|
|
|
|
if(row_orders[i] > 0)
|
|
{
|
|
profit_text = StringFormat("%+.2f", row_profit[i]);
|
|
profit_color = (row_profit[i] >= 0) ? C'127,255,212' : C'255,99,71';
|
|
}
|
|
else
|
|
{
|
|
profit_text = "+0.00";
|
|
profit_color = c_font_color;
|
|
}
|
|
|
|
ObjectSetString(0, g_object_prefix + "PROFIT_CELL_" + (string)i + "_TXT", OBJPROP_TEXT, profit_text);
|
|
ObjectSetInteger(0, g_object_prefix + "PROFIT_CELL_" + (string)i + "_TXT", OBJPROP_COLOR, profit_color);
|
|
|
|
string order_text = (string)row_orders[i];
|
|
ObjectSetString(0, g_object_prefix + "ORDER_CELL_" + (string)i + "_TXT", OBJPROP_TEXT, order_text);
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
// Close all trades for a specific pair row (by dashboard tag)
|
|
// ------------------------------------------------------------
|
|
void ClosePairTrades(const int row_index)
|
|
{
|
|
if(row_index < 0 || row_index >= TOTAL_PAIRS) return;
|
|
const string pref = g_object_prefix + "ROW:" + (string)row_index;
|
|
|
|
CPositionInfo pi;
|
|
for(int i = PositionsTotal() - 1; i >= 0; --i)
|
|
{
|
|
if(pi.SelectByIndex(i) && pi.Magic()==InpMagicNumber &&
|
|
StringFind(pi.Comment(), pref) == 0)
|
|
{
|
|
if(trade.PositionClose(pi.Ticket()))
|
|
g_last_row_close_bar[row_index] = TimeCurrent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CloseMatrixSymbolTrades(const string symbol)
|
|
{
|
|
if(symbol=="") return;
|
|
const string pref = g_object_prefix + "MATRIX:" + symbol;
|
|
CPositionInfo pi;
|
|
for(int i=PositionsTotal()-1;i>=0;--i)
|
|
if(pi.SelectByIndex(i) && pi.Magic()==InpMagicNumber &&
|
|
StringFind(pi.Comment(), pref)==0)
|
|
trade.PositionClose(pi.Ticket());
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
// Close all profitable trades managed by the dashboard
|
|
// ------------------------------------------------------------
|
|
void CloseAllProfitableTrades()
|
|
{
|
|
CPositionInfo pi;
|
|
int idx = PositionsTotal() - 1;
|
|
while(idx >= 0)
|
|
{
|
|
if(pi.SelectByIndex(idx) && pi.Magic()==InpMagicNumber)
|
|
{
|
|
const double pl = pi.Profit() + pi.Swap();
|
|
if(pl >= 0.0) trade.PositionClose(pi.Ticket());
|
|
}
|
|
idx--;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
// Close all losing trades managed by the dashboard
|
|
// ------------------------------------------------------------
|
|
void CloseAllLosingTrades()
|
|
{
|
|
CPositionInfo pi;
|
|
int idx = PositionsTotal() - 1;
|
|
while(idx >= 0)
|
|
{
|
|
if(pi.SelectByIndex(idx) && pi.Magic()==InpMagicNumber)
|
|
{
|
|
const double pl = pi.Profit() + pi.Swap();
|
|
if(pl < 0.0) trade.PositionClose(pi.Ticket());
|
|
}
|
|
idx--;
|
|
}
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Creates the main background for the entire dashboard. |
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CreateMainBackground()
|
|
{
|
|
int width = c_total_panel_w + (int)(245 * g_scale);
|
|
int height = (int)(860 * g_scale);
|
|
CreateRectangle(g_object_prefix + "MAIN_BG", 5, 5, width, height, C'0, 0, 0', CORNER_LEFT_UPPER, BORDER_FLAT, true);
|
|
}
|
|
|
|
|
|
void CreateDashboardUI()
|
|
{
|
|
CreateMainBackground();
|
|
|
|
int start_x = (int)(15 * g_scale);
|
|
int y_pos = (int)(10 * g_scale);
|
|
|
|
CreateTopPanels(start_x, y_pos);
|
|
y_pos += (int)(120 * g_scale) + c_margin;
|
|
|
|
CreateHeader(y_pos);
|
|
y_pos += c_row_h + c_margin;
|
|
|
|
for(int i = 0; i < TOTAL_PAIRS; i++)
|
|
{
|
|
CreateDataRow(i, y_pos);
|
|
y_pos += c_row_h;
|
|
}
|
|
y_pos += c_margin;
|
|
|
|
CreateAnalysisPanel(start_x, y_pos);
|
|
|
|
|
|
int right_panel_x = c_main_panel_w + (int)(20 * g_scale);
|
|
int right_y_pos = (int)(10 * g_scale);
|
|
|
|
CreateCorrelationMatrix(right_panel_x, right_y_pos);
|
|
right_y_pos += GetMatrixPixelHeight() + (int)(20 * g_scale);
|
|
|
|
int bottom_panel_x = right_panel_x + (int)(40 * g_scale) + InpRightPanelShiftX;
|
|
CreateBottomPanels(bottom_panel_x, right_y_pos);
|
|
|
|
|
|
int need_h = right_y_pos + (int)(260 * g_scale);
|
|
int cur_h = (int)ObjectGetInteger(0, g_object_prefix + "MAIN_BG", OBJPROP_YSIZE);
|
|
if(need_h > cur_h) ObjectSetInteger(0, g_object_prefix + "MAIN_BG", OBJPROP_YSIZE, need_h);
|
|
}
|
|
|
|
|
|
void CreateTopPanels(int start_x, int start_y)
|
|
{
|
|
string P_PORT = g_object_prefix + "PORT_";
|
|
int port_panel_w = (int)(440 * g_scale);
|
|
int panel_h = (int)(120 * g_scale);
|
|
|
|
CreateRectangle(P_PORT + "BG", start_x, start_y, port_panel_w, panel_h, c_header_bg, CORNER_LEFT_UPPER);
|
|
CreateLabel(P_PORT + "HEADER", "Portfolio Summary", start_x + port_panel_w / 2, start_y + (int)(8*g_scale), clrGold, ANCHOR_CENTER);
|
|
|
|
int x1 = start_x + (int)(10*g_scale);
|
|
int x2 = start_x + (int)(180*g_scale);
|
|
int y1 = start_y + (int)(25*g_scale);
|
|
int row_gap = (int)(18*g_scale);
|
|
|
|
int y2 = y1 + row_gap;
|
|
int y3 = y2 + row_gap;
|
|
int y4 = y3 + row_gap;
|
|
int x1_val = x1 + (int)(150*g_scale);
|
|
int x2_val = x2 + (int)(240*g_scale);
|
|
|
|
CreateLabel(P_PORT + "LBL_Balance", "Balance:", x1, y1, c_font_color, ANCHOR_LEFT); CreateLabel(P_PORT + "VAL_Balance", "0.00", x1_val, y1, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_PORT + "LBL_Equity", "Equity:", x1, y2, c_font_color, ANCHOR_LEFT); CreateLabel(P_PORT + "VAL_Equity", "0.00", x1_val, y2, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_PORT + "LBL_Profit", "Total Profit:", x1, y3, c_font_color, ANCHOR_LEFT); CreateLabel(P_PORT + "VAL_Profit", "0.00", x1_val, y3, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_PORT + "LBL_ProfitC", "Profit Closed:", x1, y4, c_font_color, ANCHOR_LEFT); CreateLabel(P_PORT + "VAL_ProfitC", "0.00", x1_val, y4, clrWhite, ANCHOR_RIGHT);
|
|
|
|
CreateLabel(P_PORT + "LBL_Login", "Login:", x2, y1, c_font_color, ANCHOR_LEFT); CreateLabel(P_PORT + "VAL_Login", "0", x2_val, y1, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_PORT + "LBL_Broker", "Broker:", x2, y2, c_font_color, ANCHOR_LEFT); CreateLabel(P_PORT + "VAL_Broker", "N/A", x2_val, y2, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_PORT + "LBL_User", "User:", x2, y3, c_font_color, ANCHOR_LEFT); CreateLabel(P_PORT + "VAL_User", "N/A", x2_val, y3, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_PORT + "LBL_MarginL", "Margin Level:", x2, y4, c_font_color, ANCHOR_LEFT); CreateLabel(P_PORT + "VAL_MarginL", "0.00%", x2_val, y4, clrWhite, ANCHOR_RIGHT);
|
|
|
|
string P_TIME = g_object_prefix + "TIME_";
|
|
int time_panel_x = start_x + port_panel_w + (int)(5*g_scale);
|
|
int time_panel_w = c_main_panel_w - port_panel_w - (int)(20*g_scale);
|
|
CreateRectangle(P_TIME + "BG", time_panel_x - (int)(5*g_scale), start_y, time_panel_w, panel_h, c_header_bg, CORNER_LEFT_UPPER);
|
|
CreateLabel(P_TIME + "HEADER", "Time & Market Sessions", time_panel_x + time_panel_w / 2, start_y + (int)(8*g_scale), clrGold, ANCHOR_CENTER);
|
|
|
|
int xt1 = time_panel_x + (int)(10*g_scale);
|
|
int xt2 = time_panel_x + (int)(130*g_scale);
|
|
int yt1 = start_y + (int)(25*g_scale);
|
|
int yt2 = yt1 + row_gap;
|
|
int yt3 = yt2 + row_gap;
|
|
int yt4 = yt3 + row_gap;
|
|
int xt2_val = xt2 + (int)(100*g_scale);
|
|
int xt1_val = xt1 + (int)(110*g_scale);
|
|
|
|
CreateLabel(P_TIME + "LBL_Local", "Local:", xt1, yt1, c_font_color, ANCHOR_LEFT); CreateLabel(P_TIME + "VAL_Local", "00:00:00", xt1_val, yt1, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_TIME + "LBL_Server", "Server:", xt1, yt2, c_font_color, ANCHOR_LEFT); CreateLabel(P_TIME + "VAL_Server", "00:00:00", xt1_val, yt2, clrWhite, ANCHOR_RIGHT);
|
|
|
|
CreateLabel(P_TIME + "LBL_NY", "New York:", xt2, yt1, c_font_color, ANCHOR_LEFT); CreateLabel(P_TIME + "VAL_NY", "-", xt2_val, yt1, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_TIME + "LBL_LN", "London:", xt2, yt2, c_font_color, ANCHOR_LEFT); CreateLabel(P_TIME + "VAL_LN", "-", xt2_val, yt2, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_TIME + "LBL_SY", "Sydney:", xt2, yt3, c_font_color, ANCHOR_LEFT); CreateLabel(P_TIME + "VAL_SY", "-", xt2_val, yt3, clrWhite, ANCHOR_RIGHT);
|
|
CreateLabel(P_TIME + "LBL_TK", "Tokyo:", xt2, yt4, c_font_color, ANCHOR_LEFT); CreateLabel(P_TIME + "VAL_TK", "-", xt2_val, yt4, clrWhite, ANCHOR_RIGHT);
|
|
}
|
|
|
|
|
|
void CreateBottomPanels(int start_x, int start_y)
|
|
{
|
|
string P_CTRL = g_object_prefix + "CTRL_";
|
|
int panel_w = (int)(320 * g_scale);
|
|
int gap = (int)(8 * g_scale);
|
|
int col_w = panel_w - (int)(10 * g_scale);
|
|
int x_lbl = start_x + (int)(5 * g_scale);
|
|
int h_135 = (int)(135 * g_scale);
|
|
|
|
CreateRectangle(P_CTRL + "BG", start_x, start_y, panel_w, h_135, c_header_bg, CORNER_LEFT_UPPER);
|
|
int y_r1 = start_y + (int)(10*g_scale);
|
|
int y_r2 = y_r1 + (int)(20*g_scale);
|
|
int y_r3 = y_r2 + (int)(22*g_scale);
|
|
int y_r4 = y_r3 + (int)(22*g_scale);
|
|
int y_r5 = y_r4 + (int)(26*g_scale);
|
|
|
|
CreateLabel(P_CTRL + "H_BASKET", "Basket & Money Management", start_x + panel_w/2, y_r1, clrGold, ANCHOR_CENTER, "Arial");
|
|
|
|
int btn_w_mode = (col_w - gap) / 2;
|
|
int btn_h_18 = (int)(18 * g_scale);
|
|
CreateButton(P_CTRL + "BASKET_BTN_MODE_PERCENT", "Percent %", x_lbl, y_r2, btn_w_mode, btn_h_18, g_basket_exit_mode == MODE_PERCENT ? c_btn_on_bg : c_bg_light);
|
|
CreateButton(P_CTRL + "BASKET_BTN_MODE_MONEY", "Money $", x_lbl + btn_w_mode + gap, y_r2, btn_w_mode, btn_h_18, g_basket_exit_mode == MODE_MONEY ? c_btn_on_bg : c_bg_light);
|
|
|
|
int edit_box_h = c_row_h - (int)(8*g_scale);
|
|
int label_y_offset = y_r3 + (int)((double)edit_box_h/1.5) - (g_curr_font_size/2);
|
|
|
|
int x_off_90 = (int)(90*g_scale);
|
|
int x_off_165 = (int)(165*g_scale);
|
|
int x_off_225 = (int)(225*g_scale);
|
|
int edit_w_50 = (int)(50*g_scale);
|
|
|
|
CreateLabel(P_CTRL + "LBL_TP_PERC", "Basket TP (%):", x_lbl, label_y_offset, c_font_color);
|
|
CreateEditBox(P_CTRL + "BASKET_EDIT_TP_PERC", StringFormat("%.2f", g_basket_tp_percent), x_lbl + x_off_90, y_r3, edit_w_50, edit_box_h);
|
|
CreateLabel(P_CTRL + "LBL_TP_MONEY", "TP ($):", x_lbl + x_off_165, label_y_offset, c_font_color);
|
|
CreateEditBox(P_CTRL + "BASKET_EDIT_TP_MONEY", StringFormat("%.2f", g_basket_tp_money), x_lbl + x_off_225, y_r3, edit_w_50, edit_box_h);
|
|
|
|
label_y_offset = y_r4 + (int)((double)edit_box_h/1.3) - (g_curr_font_size/2);
|
|
CreateLabel(P_CTRL + "LBL_SL_PERC", "Basket SL (%):", x_lbl, label_y_offset, c_font_color);
|
|
CreateEditBox(P_CTRL + "BASKET_EDIT_SL_PERC", StringFormat("%.2f", g_basket_sl_percent), x_lbl + x_off_90, y_r4, edit_w_50, edit_box_h);
|
|
CreateLabel(P_CTRL + "LBL_SL_MONEY", "SL ($):", x_lbl + x_off_165, label_y_offset, c_font_color);
|
|
CreateEditBox(P_CTRL + "BASKET_EDIT_SL_MONEY", StringFormat("%.2f", g_basket_sl_money), x_lbl + x_off_225, y_r4, edit_w_50, edit_box_h);
|
|
|
|
int btn_w_enable = (int)(120*g_scale);
|
|
int btn_x_enable = x_lbl + (col_w / 2) - (btn_w_enable / 2);
|
|
CreateButton(P_CTRL + "BASKET_BTN_ENABLE", g_basket_exit_enabled ? "BASKET EXIT IS ON" : "ENABLE BASKET EXIT", btn_x_enable, y_r5, btn_w_enable, btn_h_18, g_basket_exit_enabled ? c_btn_on_bg : c_btn_off_bg);
|
|
|
|
int exec_y_start = y_r5 + (int)(22*g_scale);
|
|
int h_180 = (int)(180 * g_scale);
|
|
CreateRectangle(P_CTRL + "EXEC_BG", start_x, exec_y_start, panel_w, h_180, c_header_bg, CORNER_LEFT_UPPER);
|
|
CreateLabel(P_CTRL + "H_EXECUTION", "Trade Execution & Timeframe", start_x + panel_w/2, exec_y_start + (int)(8*g_scale), clrGold, ANCHOR_CENTER, "Arial");
|
|
|
|
int exec_y2 = exec_y_start + (int)(20*g_scale);
|
|
int exec_y3 = exec_y2 + (int)(22*g_scale);
|
|
int exec_y4 = exec_y3 + (int)(25*g_scale);
|
|
int exec_y5 = exec_y4 + (int)(22*g_scale);
|
|
int exec_y5b = exec_y5 + (int)(22*g_scale);
|
|
int exec_y6 = exec_y5b + (int)(24*g_scale);
|
|
|
|
int btn_w_half = (col_w - gap) / 2;
|
|
int x_col2_btn = x_lbl + btn_w_half + gap;
|
|
|
|
CreateButton(P_CTRL + "TRADE_SETTINGS_BTN_LOT_FIXED", "FIXED LOT", x_lbl, exec_y2, btn_w_half, btn_h_18, g_lot_mode == LOT_MODE_FIXED ? c_btn_on_bg : c_btn_off_bg);
|
|
CreateButton(P_CTRL + "TRADE_SETTINGS_BTN_LOT_AUTO", "AUTO RISK %", x_col2_btn, exec_y2, btn_w_half, btn_h_18, g_lot_mode == LOT_MODE_AUTO ? c_btn_on_bg : c_btn_off_bg);
|
|
|
|
CreateEditBox(P_CTRL + "EDIT_FIXED_LOT", StringFormat("%.2f", g_fixed_lot_size), x_lbl, exec_y3, btn_w_half, btn_h_18);
|
|
CreateEditBox(P_CTRL + "EDIT_RISK_PERC", StringFormat("%.2f", g_risk_percent), x_col2_btn, exec_y3, btn_w_half, btn_h_18);
|
|
|
|
CreateButton(P_CTRL + "TRADE_SETTINGS_BTN_TIME_EXIT", "Time Exit", x_lbl, exec_y4, btn_w_half, btn_h_18, g_use_time_exit ? c_btn_on_bg : c_btn_off_bg);
|
|
CreateButton(P_CTRL + "TRADE_SETTINGS_BTN_CORR_DECAY", "Corr Decay", x_col2_btn, exec_y4, btn_w_half, btn_h_18, g_use_corr_decay_exit? c_btn_on_bg : c_btn_off_bg);
|
|
|
|
int btn_w_mtf = (int)(150*g_scale);
|
|
int btn_h_20 = (int)(20*g_scale);
|
|
int btn_x_mtf = x_lbl + (col_w/2) - (btn_w_mtf/2);
|
|
CreateButton(P_CTRL + "TRADE_SETTINGS_BTN_MTF_FILTER", "MTF Filter", btn_x_mtf, exec_y5, btn_w_mtf, btn_h_20, g_use_mtf_filter ? c_btn_on_bg : c_btn_off_bg);
|
|
|
|
int btn_w_sltp = (int)(150*g_scale);
|
|
int btn_x_sltp = x_lbl + (col_w/2) - (btn_w_sltp/2);
|
|
string cap_sltp = g_use_sl_tp ? ("SL/TP (ON • RR " + StringFormat("%.2f", g_rr) + ")") : "SL/TP (OFF)";
|
|
CreateButton(P_CTRL + "TRADE_SETTINGS_BTN_SLTP", cap_sltp, btn_x_sltp, exec_y5b, btn_w_sltp, btn_h_20, g_use_sl_tp ? c_btn_on_bg : c_btn_off_bg);
|
|
|
|
int btn_w_tf = (int)(35*g_scale);
|
|
int btn_w_reload = (int)(55*g_scale);
|
|
int total_tf_width = (5 * btn_w_tf) + (4 * gap) + btn_w_reload + (int)(5*g_scale);
|
|
int x_pos_tf = x_lbl + (col_w/2) - (total_tf_width/2);
|
|
|
|
ENUM_TIMEFRAMES tfs_enum[] = {PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1};
|
|
for(int ii=0; ii<ArraySize(tfs_enum); ii++)
|
|
{
|
|
string tf_str = EnumToString(tfs_enum[ii]); StringReplace(tf_str, "PERIOD_", "");
|
|
CreateButton(P_CTRL + "TF_" + tf_str, tf_str, x_pos_tf, exec_y6, btn_w_tf, btn_h_18, (tfs_enum[ii] == g_current_timeframe ? c_btn_on_bg : c_bg_light));
|
|
x_pos_tf += btn_w_tf + gap;
|
|
}
|
|
CreateButton(P_CTRL + "RELOAD", "Reload", x_pos_tf + (int)(5*g_scale), exec_y6, btn_w_reload, btn_h_18, c_btn_bs_sb_bg);
|
|
|
|
int close_y_start = exec_y6 + (int)(22*g_scale);
|
|
int h_60 = (int)(60*g_scale);
|
|
CreateRectangle(P_CTRL + "CLOSE_BG", start_x, close_y_start, panel_w, h_60, c_header_bg, CORNER_LEFT_UPPER);
|
|
CreateLabel(P_CTRL + "H_CLOSE_CONTROLS", "Portfolio Close Controls", start_x + panel_w / 2, close_y_start + (int)(8*g_scale), clrGold, ANCHOR_CENTER, "Arial");
|
|
|
|
int close_y_btn = close_y_start + (int)(25*g_scale);
|
|
int btn_w_third = (col_w - (gap * 2)) / 3;
|
|
int btn_h_25 = (int)(25*g_scale);
|
|
|
|
CreateButton(P_CTRL + "CLOSE_PROFIT_BTN", "CLOSE PROFIT", x_lbl, close_y_btn, btn_w_third, btn_h_25, c_btn_on_bg);
|
|
CreateButton(P_CTRL + "CLOSE_LOSS_BTN", "CLOSE LOSS", x_lbl + btn_w_third + gap, close_y_btn, btn_w_third, btn_h_25, C'255,100,0');
|
|
CreateButton(P_CTRL + "CLOSE_ALL_BTN", "CLOSE ALL", x_lbl + (btn_w_third * 2) + (gap * 2), close_y_btn, btn_w_third, btn_h_25, c_btn_off_bg);
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Creates the header for the main correlation table. |
|
|
//+------------------------------------------------------------------+
|
|
void CreateHeader(int y_pos)
|
|
{
|
|
CreateCellWithText(g_object_prefix+"H_CLOSE","CLOSE",c_col_close_x,y_pos,c_col_close_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_PROFIT","PROFIT",c_col_profit_x,y_pos,c_col_profit_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_ORDER","ORDER",c_col_order_x,y_pos,c_col_order_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_ENTRY_B","ENTRY(B)",c_col_entry_buy_x,y_pos,c_col_entry_buy_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_AUTO","AUTO",c_col_auto_x,y_pos,c_col_auto_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_ENTRY_S","ENTRY(S)",c_col_entry_sell_x,y_pos,c_col_entry_sell_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_CORR","CORRELATION",c_col_corr_x,y_pos,c_col_corr_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_CURR_CORR","Corr",c_col_current_corr_x,y_pos,c_col_current_corr_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_DELTA_CORR","ΔCorr",c_col_delta_corr_x,y_pos,c_col_delta_corr_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_SPRD_DIV","Sprd Div",c_col_sprd_div_x,y_pos,c_col_sprd_div_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix+"H_CONF","CONF%",c_col_confidence_x,y_pos,c_col_confidence_w,c_row_h,c_header_bg,c_font_color,g_curr_font_size,ANCHOR_CENTER);
|
|
}
|
|
|
|
|
|
void CreateDataRow(int index, int y_pos)
|
|
{
|
|
color bg_color = (index % 2 == 0) ? c_bg_dark : c_bg_light;
|
|
|
|
CreateButton(g_object_prefix + "CLOSE_BTN_" + (string)index, "X", c_col_close_x, y_pos, c_col_close_w, c_row_h, C'60,0,0');
|
|
CreateCellWithText(g_object_prefix + "PROFIT_CELL_" + (string)index, "0.00", c_col_profit_x, y_pos, c_col_profit_w, c_row_h, bg_color, c_font_color, g_curr_font_size, ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix + "ORDER_CELL_" + (string)index, "0", c_col_order_x, y_pos, c_col_order_w, c_row_h, bg_color, c_font_color, g_curr_font_size, ANCHOR_CENTER);
|
|
|
|
int small_btn_w = (c_col_entry_buy_w - c_margin) / 2;
|
|
CreateButton(g_object_prefix + "BB_BTN_" + (string)index, "BB", c_col_entry_buy_x, y_pos, small_btn_w, c_row_h, c_btn_bb_ss_bg);
|
|
CreateButton(g_object_prefix + "BS_BTN_" + (string)index, "BS", c_col_entry_buy_x + small_btn_w + c_margin, y_pos, small_btn_w, c_row_h, c_btn_bs_sb_bg);
|
|
CreateButton(g_object_prefix + "AUTO_BTN_" + (string)index, "off", c_col_auto_x, y_pos, c_col_auto_w, c_row_h, c_btn_off_bg);
|
|
CreateButton(g_object_prefix + "SS_BTN_" + (string)index, "SS", c_col_entry_sell_x, y_pos, small_btn_w, c_row_h, c_btn_bb_ss_bg);
|
|
CreateButton(g_object_prefix + "SB_BTN_" + (string)index, "SB", c_col_entry_sell_x + small_btn_w + c_margin, y_pos, small_btn_w, c_row_h, c_btn_bs_sb_bg);
|
|
|
|
string corr_text = "Set " + StringFormat("%02d", index + 1) + " " + g_pairs[index][0] + "/" + g_pairs[index][1];
|
|
CreateCellWithText(g_object_prefix + "CORR_CELL_" + (string)index, corr_text, c_col_corr_x, y_pos, c_col_corr_w, c_row_h, bg_color, c_font_color, g_curr_font_size, ANCHOR_LEFT);
|
|
CreateCellWithText(g_object_prefix + "CURR_CORR_CELL_" + (string)index, "...", c_col_current_corr_x, y_pos, c_col_current_corr_w, c_row_h, bg_color, c_font_color, g_curr_font_size, ANCHOR_CENTER);
|
|
ObjectSetInteger(0, g_object_prefix + "CURR_CORR_CELL_" + (string)index + "_BG", OBJPROP_BACK, false);
|
|
CreateCellWithText(g_object_prefix + "DELTA_CORR_CELL_" + (string)index, "...", c_col_delta_corr_x, y_pos, c_col_delta_corr_w, c_row_h, bg_color, c_font_color, g_curr_font_size, ANCHOR_CENTER);
|
|
ObjectSetInteger(0, g_object_prefix + "DELTA_CORR_CELL_" + (string)index + "_BG", OBJPROP_BACK, false);
|
|
CreateCellWithText(g_object_prefix + "SPRD_DIV_CELL_" + (string)index, "-", c_col_sprd_div_x, y_pos, c_col_sprd_div_w, c_row_h, bg_color, c_font_color, g_curr_font_size, ANCHOR_CENTER);
|
|
CreateCellWithText(g_object_prefix + "CONF_CELL_" + (string)index, "-", c_col_confidence_x, y_pos, c_col_confidence_w, c_row_h, bg_color, c_font_color, g_curr_font_size, ANCHOR_CENTER);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Creates the analysis panel with top signals and opportunity finder. |
|
|
//+------------------------------------------------------------------+
|
|
void CreateAnalysisPanel(int start_x, int start_y)
|
|
{
|
|
string P_ANLS = g_object_prefix + "ANALYSIS_";
|
|
int panel_w = c_main_panel_w - (int)(20*g_scale);
|
|
int panel_h = (int)(200*g_scale);
|
|
CreateRectangle(P_ANLS + "BG", start_x, start_y, panel_w, panel_h, c_header_bg, CORNER_LEFT_UPPER);
|
|
|
|
int sig_panel_w = (panel_w - (c_margin*4)) / 3;
|
|
int current_x = start_x + c_margin;
|
|
int header_y = start_y + (int)(10*g_scale);
|
|
int data_y = start_y + (int)(30*g_scale);
|
|
|
|
CreateLabel(P_ANLS+"SIG_POS_H", "Top Positive (>+0.75)", current_x+(int)(5*g_scale), header_y, clrGold, ANCHOR_LEFT, "Arial");
|
|
CreateLabel(P_ANLS+"SIG_POS_L1", "1. (None)", current_x+(int)(5*g_scale), data_y, c_font_color, ANCHOR_LEFT, "Arial");
|
|
|
|
current_x += sig_panel_w + c_margin;
|
|
CreateLabel(P_ANLS+"SIG_NEG_H", "Top Negative (<-0.75)", current_x+(int)(5*g_scale), header_y, clrGold, ANCHOR_LEFT, "Arial");
|
|
CreateLabel(P_ANLS+"SIG_NEG_L1", "1. (None)", current_x+(int)(5*g_scale), data_y, c_font_color, ANCHOR_LEFT, "Arial");
|
|
|
|
current_x += sig_panel_w + c_margin;
|
|
CreateLabel(P_ANLS+"SIG_DEL_H", "Delta Corr Spikes (>|0.1|)", current_x+(int)(5*g_scale), header_y, clrGold, ANCHOR_LEFT, "Arial");
|
|
CreateLabel(P_ANLS+"SIG_DEL_L1", "1. (None)", current_x+(int)(5*g_scale), data_y, c_font_color, ANCHOR_LEFT, "Arial");
|
|
|
|
string P_OPP = P_ANLS + "OPP_";
|
|
int opp_y_start = start_y + (int)(55*g_scale);
|
|
CreateLabel(P_OPP + "HEADER", "Opportunity Finder (from Matrix)", start_x + (int)(10*g_scale), opp_y_start, clrGold, ANCHOR_LEFT, "Arial");
|
|
|
|
int opp_y = opp_y_start + (int)(14*g_scale);
|
|
int x_rank = start_x + (int)(10*g_scale);
|
|
int x_pair = start_x + (int)(45*g_scale);
|
|
int x_corr = start_x + (int)(135*g_scale);
|
|
int x_style = start_x + (int)(225*g_scale);
|
|
int x_signal = start_x + (int)(355*g_scale);
|
|
int x_btn = start_x + (int)(470*g_scale);
|
|
int opp_row_h = (int)(18*g_scale);
|
|
int btn_w_80 = (int)(80*g_scale);
|
|
int x_off_150 = (int)(150*g_scale);
|
|
|
|
for(int ii=0; ii<5; ii++)
|
|
{
|
|
int centered_y = opp_y + (int)((double)opp_row_h/1.35) - g_curr_font_size/2;
|
|
CreateLabel(P_OPP+"RANK_"+(string)ii, "#"+(string)(ii+1), x_rank, centered_y, c_font_color);
|
|
CreateLabel(P_OPP+"PAIR_"+(string)ii, "-", x_pair, centered_y, clrWhite);
|
|
CreateLabel(P_OPP+"CORR_"+(string)ii, "Corr: -", x_corr, centered_y, c_font_color);
|
|
CreateLabel(P_OPP+"STYLE_"+(string)ii, "Style: -", x_style, centered_y, clrAquamarine);
|
|
CreateLabel(P_OPP+"SIGNAL_"+(string)ii, "-", x_signal, centered_y, c_font_color);
|
|
|
|
CreateButton(P_OPP+"TRADE_BTN_"+(string)ii, "[ TRADE ]", x_btn, opp_y, btn_w_80, opp_row_h, c_btn_bb_ss_bg);
|
|
CreateLabel(P_OPP+"CONF_"+(string)ii, "CONF: -", x_btn + x_off_150, centered_y, c_font_color);
|
|
opp_y += opp_row_h + (int)(4*g_scale);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Updates the Opportunity Finder panel. |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateOpportunityPanel()
|
|
{
|
|
string P = g_object_prefix + "ANALYSIS_OPP_";
|
|
for(int ii = 0; ii < 5; ii++)
|
|
{
|
|
string btn_name = P + "TRADE_BTN_" + (string)ii;
|
|
|
|
if(g_opportunities[ii].trade_action != SIGNAL_NONE)
|
|
{
|
|
string symbol = g_opportunities[ii].symbol;
|
|
ObjectSetString(0, P + "PAIR_" + (string)ii, OBJPROP_TEXT, symbol);
|
|
ObjectSetString(0, P + "CORR_" + (string)ii, OBJPROP_TEXT, "Corr: " + StringFormat("%+.2f", g_opportunities[ii].correlation));
|
|
ObjectSetString(0, P + "STYLE_" + (string)ii, OBJPROP_TEXT, "Style: " + g_opportunities[ii].style);
|
|
|
|
string signal_text = "";
|
|
string btn_text = "";
|
|
color btn_color = c_btn_bb_ss_bg;
|
|
|
|
if(g_opportunities[ii].trade_action == SIGNAL_BS)
|
|
{
|
|
signal_text = "Signal: BUY " + symbol;
|
|
btn_text = "[ BUY " + symbol + " ]";
|
|
btn_color = c_btn_on_bg;
|
|
}
|
|
else if(g_opportunities[ii].trade_action == SIGNAL_SB)
|
|
{
|
|
signal_text = "Signal: SELL " + symbol;
|
|
btn_text = "[ SELL " + symbol + " ]";
|
|
btn_color = c_btn_off_bg;
|
|
}
|
|
|
|
ObjectSetString(0, P + "SIGNAL_" + (string)ii, OBJPROP_TEXT, signal_text);
|
|
ObjectSetString(0, btn_name, OBJPROP_TEXT, btn_text);
|
|
ObjectSetInteger(0, btn_name, OBJPROP_XSIZE, 120);
|
|
ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, btn_color);
|
|
|
|
double conf = g_opportunities[ii].confidence;
|
|
ObjectSetString(0, P + "CONF_" + (string)ii, OBJPROP_TEXT, "CONF: " + StringFormat("%.0f%%", conf));
|
|
color conf_clr = (conf >= 70) ? C'30,255,150' : (conf >= 40 ? clrOrange : C'255,99,71');
|
|
ObjectSetInteger(0, P + "CONF_" + (string)ii, OBJPROP_COLOR, conf_clr);
|
|
}
|
|
else
|
|
{
|
|
ObjectSetString(0, P + "PAIR_" + (string)ii, OBJPROP_TEXT, "-");
|
|
ObjectSetString(0, P + "CORR_" + (string)ii, OBJPROP_TEXT, "Corr: -");
|
|
ObjectSetString(0, P + "STYLE_" + (string)ii, OBJPROP_TEXT, "Style: -");
|
|
ObjectSetString(0, P + "SIGNAL_" + (string)ii, OBJPROP_TEXT, "-");
|
|
|
|
ObjectSetString(0, btn_name, OBJPROP_TEXT, "[ TRADE ]");
|
|
ObjectSetInteger(0, btn_name, OBJPROP_XSIZE, 80);
|
|
ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, c_btn_bb_ss_bg);
|
|
|
|
ObjectSetString(0, P + "CONF_" + (string)ii, OBJPROP_TEXT, "CONF: -");
|
|
ObjectSetInteger(0, P + "CONF_" + (string)ii, OBJPROP_COLOR, c_font_color);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int MakeHA(const string sym, ENUM_TIMEFRAMES tf)
|
|
{
|
|
int h = iCustom(sym, tf, "Examples\\Heiken_Ashi");
|
|
if(h == INVALID_HANDLE) h = iCustom(sym, tf, "Heiken_Ashi");
|
|
return h;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Recreates all indicator handles when timeframe changes. |
|
|
//+------------------------------------------------------------------+
|
|
|
|
void RecreateIndicatorHandles()
|
|
{
|
|
Print("Recreating indicator handles. Main TF: ", EnumToString(g_current_timeframe),
|
|
", Conf TF: ", EnumToString(InpTimeframe_Confirmation));
|
|
|
|
for(int i = 0; i < TOTAL_PAIRS; i++)
|
|
{
|
|
|
|
IndicatorRelease(g_ha_handles_main[i][0]);
|
|
IndicatorRelease(g_ha_handles_main[i][1]);
|
|
IndicatorRelease(g_ha_handles_conf[i][0]);
|
|
IndicatorRelease(g_ha_handles_conf[i][1]);
|
|
|
|
|
|
g_ha_handles_main[i][0] = MakeHA(g_pairs[i][0], g_current_timeframe);
|
|
g_ha_handles_main[i][1] = MakeHA(g_pairs[i][1], g_current_timeframe);
|
|
|
|
g_ha_handles_conf[i][0] = MakeHA(g_pairs[i][0], InpTimeframe_Confirmation);
|
|
g_ha_handles_conf[i][1] = MakeHA(g_pairs[i][1], InpTimeframe_Confirmation);
|
|
|
|
|
|
g_adx_handles_main[i][0] = iADX(g_pairs[i][0], g_current_timeframe, InpADX_Period);
|
|
g_adx_handles_main[i][1] = iADX(g_pairs[i][1], g_current_timeframe, InpADX_Period);
|
|
}
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Updates the main dashboard data points. |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateDashboardData()
|
|
{
|
|
for(int i=0;i<TOTAL_PAIRS;i++)
|
|
{
|
|
double corr=g_corr_data[i].current_corr;
|
|
double delta=g_corr_data[i].delta_corr;
|
|
string corr_text=StringFormat("%+.2f",corr);
|
|
if(g_last_corr_text[i]!=corr_text)
|
|
{
|
|
ObjectSetString(0,g_object_prefix+"CURR_CORR_CELL_"+(string)i+"_TXT",OBJPROP_TEXT,corr_text);
|
|
g_last_corr_text[i]=corr_text;
|
|
}
|
|
string delta_text=StringFormat("%+.3f",delta);
|
|
if(g_last_delta_text[i]!=delta_text)
|
|
{
|
|
ObjectSetString(0,g_object_prefix+"DELTA_CORR_CELL_"+(string)i+"_TXT",OBJPROP_TEXT,delta_text);
|
|
g_last_delta_text[i]=delta_text;
|
|
}
|
|
color corr_color=c_font_color;
|
|
if(corr>=0.7)corr_color=C'30,255,150';
|
|
else if(corr>0.3)corr_color=C'152,251,152';
|
|
else if(corr<=-0.7)corr_color=C'255,99,71';
|
|
else if(corr<-0.3)corr_color=C'250,128,114';
|
|
if(g_last_corr_color[i]!=corr_color)
|
|
{
|
|
ObjectSetInteger(0,g_object_prefix+"CURR_CORR_CELL_"+(string)i+"_TXT",OBJPROP_COLOR,corr_color);
|
|
g_last_corr_color[i]=corr_color;
|
|
}
|
|
color delta_color=c_font_color;
|
|
if(MathAbs(delta)>0.1)delta_color=clrOrange;
|
|
if(g_last_delta_color[i]!=delta_color)
|
|
{
|
|
ObjectSetInteger(0,g_object_prefix+"DELTA_CORR_CELL_"+(string)i+"_TXT",OBJPROP_COLOR,delta_color);
|
|
g_last_delta_color[i]=delta_color;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Updates the top Portfolio Summary panel. |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateInfoPanel()
|
|
{
|
|
string P_PORT = g_object_prefix + "PORT_";
|
|
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double credit = AccountInfoDouble(ACCOUNT_CREDIT);
|
|
|
|
|
|
double total_profit = AccountInfoDouble(ACCOUNT_PROFIT);
|
|
|
|
|
|
|
|
double margin_level = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
|
|
|
|
|
|
double total_lots = 0;
|
|
int total_orders = 0;
|
|
CPositionInfo pos_info;
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(pos_info.SelectByIndex(i) && pos_info.Magic() == InpMagicNumber)
|
|
{
|
|
total_orders++;
|
|
total_lots += pos_info.Volume();
|
|
}
|
|
}
|
|
|
|
double closed_profit; int closed_count;
|
|
CalculateClosedProfitAndCount(closed_profit, closed_count);
|
|
|
|
long login = AccountInfoInteger(ACCOUNT_LOGIN);
|
|
string broker_name = AccountInfoString(ACCOUNT_SERVER);
|
|
string user_name = AccountInfoString(ACCOUNT_NAME);
|
|
|
|
ObjectSetString(0, P_PORT + "VAL_Balance", OBJPROP_TEXT, StringFormat("%.2f", balance));
|
|
ObjectSetString(0, P_PORT + "VAL_Equity", OBJPROP_TEXT, StringFormat("%.2f", equity));
|
|
|
|
ObjectSetString(0, P_PORT + "VAL_Profit", OBJPROP_TEXT, StringFormat("%+.2f", total_profit));
|
|
ObjectSetInteger(0, P_PORT + "VAL_Profit", OBJPROP_COLOR, (total_profit >= 0) ? C'127,255,212' : C'255,99,71');
|
|
|
|
ObjectSetString(0, P_PORT + "VAL_ProfitC", OBJPROP_TEXT, StringFormat("%+.2f (%d)", closed_profit, closed_count));
|
|
ObjectSetInteger(0, P_PORT + "VAL_ProfitC", OBJPROP_COLOR, (closed_profit >= 0) ? C'127,255,212' : C'255,99,71');
|
|
|
|
ObjectSetString(0, P_PORT + "VAL_Login", OBJPROP_TEXT, (string)login);
|
|
ObjectSetString(0, P_PORT + "VAL_Broker", OBJPROP_TEXT, broker_name);
|
|
ObjectSetString(0, P_PORT + "VAL_User", OBJPROP_TEXT, user_name);
|
|
ObjectSetString(0, P_PORT + "VAL_MarginL",OBJPROP_TEXT, StringFormat("%.2f%%", margin_level));
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Creates the correlation matrix grid. |
|
|
//+------------------------------------------------------------------+
|
|
void CreateCorrelationMatrix(int start_x, int start_y)
|
|
{
|
|
int cell_size = (int)(40 * g_scale);
|
|
for(int i=0; i<ArraySize(g_matrix_symbols); i++)
|
|
{
|
|
CreateLabel(g_object_prefix+"MATRIX_H_COL_"+(string)i, g_matrix_symbols[i], start_x + cell_size/2 + (i+1)*cell_size, start_y + cell_size/2, c_font_color, ANCHOR_CENTER, "Arial", g_curr_font_size);
|
|
CreateLabel(g_object_prefix+"MATRIX_H_ROW_"+(string)i, g_matrix_symbols[i], start_x + cell_size/2, start_y + cell_size/2 + (i+1)*cell_size, c_font_color, ANCHOR_CENTER, "Arial", g_curr_font_size);
|
|
}
|
|
for(int r=0; r<ArraySize(g_matrix_symbols); r++)
|
|
{
|
|
for(int c=0; c<ArraySize(g_matrix_symbols); c++)
|
|
{
|
|
string name = g_object_prefix + "MATRIX_CELL_"+(string)r+"_"+(string)c;
|
|
CreateCellWithText(name, "...", start_x + (c+1)*cell_size, start_y + (r+1)*cell_size, cell_size, cell_size, c_bg_dark, c_font_color, g_curr_font_size-1, ANCHOR_CENTER);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Updates the Time & Market Sessions panel. |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateTimePanel()
|
|
{
|
|
string P_TIME = g_object_prefix + "TIME_";
|
|
datetime server_time = TimeCurrent();
|
|
datetime gmt_time = TimeGMT();
|
|
MqlDateTime gmt_struct;
|
|
TimeToStruct(gmt_time, gmt_struct);
|
|
int current_hour_gmt = gmt_struct.hour;
|
|
|
|
ObjectSetString(0, P_TIME + "VAL_Local", OBJPROP_TEXT, TimeToString(TimeLocal(), TIME_SECONDS));
|
|
ObjectSetString(0, P_TIME + "VAL_Server", OBJPROP_TEXT, TimeToString(server_time, TIME_SECONDS));
|
|
|
|
string ny_status = IsSessionOpen(NY_OPEN, NY_CLOSE, current_hour_gmt) ? "Open" : "Closed";
|
|
string ln_status = IsSessionOpen(LONDON_OPEN, LONDON_CLOSE, current_hour_gmt) ? "Open" : "Closed";
|
|
string sy_status = IsSessionOpen(SYDNEY_OPEN, SYDNEY_CLOSE, current_hour_gmt) ? "Open" : "Closed";
|
|
string tk_status = IsSessionOpen(TOKYO_OPEN, TOKYO_CLOSE, current_hour_gmt) ? "Open" : "Closed";
|
|
|
|
ObjectSetString(0, P_TIME + "VAL_NY", OBJPROP_TEXT, ny_status);
|
|
ObjectSetString(0, P_TIME + "VAL_LN", OBJPROP_TEXT, ln_status);
|
|
ObjectSetString(0, P_TIME + "VAL_SY", OBJPROP_TEXT, sy_status);
|
|
ObjectSetString(0, P_TIME + "VAL_TK", OBJPROP_TEXT, tk_status);
|
|
|
|
ObjectSetInteger(0, P_TIME + "VAL_NY", OBJPROP_COLOR, (ny_status == "Open") ? c_btn_on_bg : c_btn_off_bg);
|
|
ObjectSetInteger(0, P_TIME + "VAL_LN", OBJPROP_COLOR, (ln_status == "Open") ? c_btn_on_bg : c_btn_off_bg);
|
|
ObjectSetInteger(0, P_TIME + "VAL_SY", OBJPROP_COLOR, (sy_status == "Open") ? c_btn_on_bg : c_btn_off_bg);
|
|
ObjectSetInteger(0, P_TIME + "VAL_TK", OBJPROP_COLOR, (tk_status == "Open") ? c_btn_on_bg : c_btn_off_bg);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Closes the correlation history graph window. |
|
|
//+------------------------------------------------------------------+
|
|
void CloseCorrelationGraph()
|
|
{
|
|
ObjectsDeleteAll(0, g_object_prefix + "Graph_");
|
|
g_active_graph_row = -1;
|
|
ChartRedraw();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Shows a pop-up graph of the correlation history for a given row. |
|
|
//+------------------------------------------------------------------+
|
|
void ShowCorrelationGraph(int row_index)
|
|
{
|
|
CloseCorrelationGraph();
|
|
if(row_index < 0 || row_index >= TOTAL_PAIRS || ArraySize(g_corr_data[row_index].history) < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_active_graph_row = row_index;
|
|
string P = g_object_prefix + "Graph_";
|
|
|
|
int graph_w = 250, graph_h = 150;
|
|
int graph_x = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) / 2 - graph_w / 2;
|
|
int graph_y = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) / 2 - graph_h / 2;
|
|
color bg_color = C'15,25,45', border_color = C'80,100,130';
|
|
|
|
CreateRectangle(P + "BG", graph_x, graph_y, graph_w, graph_h, bg_color, CORNER_LEFT_UPPER, BORDER_FLAT, false);
|
|
ObjectSetInteger(0, P + "BG", OBJPROP_BORDER_COLOR, border_color);
|
|
string title = "Recent Corr. History: " + g_corr_data[row_index].symbol1 + "/" + g_corr_data[row_index].symbol2;
|
|
CreateLabel(P + "Title", title, graph_x + 10, graph_y + 8, clrGold, ANCHOR_LEFT, "Arial", 9);
|
|
CreateButton(P + "CloseBtn", "X", graph_x + graph_w - 25, graph_y + 5, 20, 20, c_btn_off_bg);
|
|
|
|
int num_to_show = 5;
|
|
int history_size = ArraySize(g_corr_data[row_index].history);
|
|
int start_index = MathMax(0, history_size - num_to_show);
|
|
|
|
int current_y = graph_y + 35;
|
|
int line_height = 18;
|
|
|
|
CreateLabel(P + "Header", " Tick Value", graph_x + 10, current_y, C'180,180,180', ANCHOR_LEFT, "Courier New", 9);
|
|
current_y += line_height;
|
|
|
|
int tick_counter = 1;
|
|
for(int i = start_index; i < history_size; i++)
|
|
{
|
|
string corr_value_text = StringFormat("%+.4f", g_corr_data[row_index].history[i]);
|
|
string line_text = StringFormat(" %-10s %s", "Tick "+(string)tick_counter, corr_value_text);
|
|
|
|
CreateLabel(P + "HistoryLine_" + (string)i, line_text, graph_x + 10, current_y, clrWhite, ANCHOR_LEFT, "Courier New", 9);
|
|
|
|
current_y += line_height;
|
|
tick_counter++;
|
|
}
|
|
ChartRedraw();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Helper Functions |
|
|
//+------------------------------------------------------------------+
|
|
string SignalTypeToString(ENUM_SIGNAL_TYPE signal_type)
|
|
{
|
|
switch(signal_type)
|
|
{
|
|
case SIGNAL_BB: return "BB";
|
|
case SIGNAL_SS: return "SS";
|
|
case SIGNAL_BS: return "BS";
|
|
case SIGNAL_SB: return "SB";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
int GetRowIndexFromSymbol(string symbol)
|
|
{
|
|
for(int i = 0; i < TOTAL_PAIRS; i++)
|
|
{
|
|
if(g_pairs[i][0] == symbol || g_pairs[i][1] == symbol)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void ResetAllHighlights(int index)
|
|
{
|
|
ObjectSetInteger(0, g_object_prefix + "BB_BTN_" + (string)index, OBJPROP_BGCOLOR, c_btn_bb_ss_bg);
|
|
ObjectSetInteger(0, g_object_prefix + "SS_BTN_" + (string)index, OBJPROP_BGCOLOR, c_btn_bb_ss_bg);
|
|
ObjectSetInteger(0, g_object_prefix + "BS_BTN_" + (string)index, OBJPROP_BGCOLOR, c_btn_bs_sb_bg);
|
|
ObjectSetInteger(0, g_object_prefix + "SB_BTN_" + (string)index, OBJPROP_BGCOLOR, c_btn_bs_sb_bg);
|
|
}
|
|
|
|
void UpdateSignalHighlight(int index, string button_to_highlight)
|
|
{
|
|
ResetAllHighlights(index);
|
|
if(button_to_highlight == "")
|
|
return;
|
|
|
|
string btn_name = g_object_prefix + button_to_highlight + "_BTN_" + (string)index;
|
|
ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, c_btn_highlight_bg);
|
|
}
|
|
|
|
bool ConfirmForce(const string title, const string body)
|
|
{
|
|
int r = MessageBox(body, title, MB_OKCANCEL|MB_ICONWARNING);
|
|
return (r==IDOK);
|
|
}
|
|
|
|
void NotifyUser(const string msg)
|
|
{
|
|
Print("[OracleDash] ", msg);
|
|
}
|
|
string LegLabel(const ENUM_ORDER_TYPE t)
|
|
{
|
|
return (t==ORDER_TYPE_BUY ? "BUY" : "SELL");
|
|
}
|
|
bool HasOpenForRow(const int row)
|
|
{
|
|
CPositionInfo pi;
|
|
string pref = g_object_prefix + "ROW:" + (string)row;
|
|
for(int i=PositionsTotal()-1; i>=0; --i)
|
|
if(pi.SelectByIndex(i) && pi.Magic()==InpMagicNumber &&
|
|
StringFind(pi.Comment(), pref) == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int RowOpenCount(const int row){
|
|
int cnt=0; CPositionInfo pi;
|
|
string pref = g_object_prefix + "ROW:" + (string)row;
|
|
for(int i=PositionsTotal()-1;i>=0;--i)
|
|
if(pi.SelectByIndex(i) && pi.Magic()==InpMagicNumber &&
|
|
StringFind(pi.Comment(), pref)==0) cnt++;
|
|
return cnt;
|
|
}
|
|
|
|
|
|
double Clamp01(double v)
|
|
{
|
|
if(v < 0.0) return 0.0;
|
|
if(v > 1.0) return 1.0;
|
|
return v;
|
|
}
|
|
|
|
double SmoothStep01(double t)
|
|
{
|
|
t = Clamp01(t);
|
|
return t*t*(3.0 - 2.0*t);
|
|
}
|
|
|
|
double SoftCap100(double x, double k)
|
|
{
|
|
if(k <= 1e-6) k = 1.0;
|
|
if(x <= 0.0) return 0.0;
|
|
double v = 100.0 * (1.0 - MathExp(-x / k));
|
|
if(v > 100.0) v = 100.0;
|
|
return v;
|
|
}
|
|
|
|
double GetSpreadPts(const string sym)
|
|
{
|
|
MqlTick tk; if(!SymbolInfoTick(sym, tk)) return 0.0;
|
|
double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
|
|
if(pt <= 0) return 0.0;
|
|
return (tk.ask - tk.bid) / pt;
|
|
}
|
|
|
|
bool IsMajorSessionOpen()
|
|
{
|
|
datetime gmt = TimeGMT();
|
|
MqlDateTime st; TimeToStruct(gmt, st);
|
|
int h = st.hour;
|
|
return IsSessionOpen(LONDON_OPEN, LONDON_CLOSE, h) || IsSessionOpen(NY_OPEN, NY_CLOSE, h);
|
|
}
|
|
|
|
double AdjustConfidenceByLogic(double conf)
|
|
{
|
|
double pct = InpLogicUsePercent;
|
|
if(pct < 10.0) pct = 10.0;
|
|
if(pct > 200.0) pct = 200.0;
|
|
|
|
double scale = 100.0 / pct;
|
|
double out = conf * scale;
|
|
|
|
if(out > 100.0) out = 100.0;
|
|
if(out < 0.0) out = 0.0;
|
|
return out;
|
|
}
|
|
|
|
bool EnsureSymbolSelected(const string sym)
|
|
{
|
|
if(sym=="") return false;
|
|
if((bool)SymbolInfoInteger(sym, SYMBOL_SELECT)) return true;
|
|
return SymbolSelect(sym, true);
|
|
}
|
|
|
|
bool DetectSymbolSuffix(string &out_suffix)
|
|
{
|
|
if(InpSymbolSuffix != "")
|
|
{
|
|
out_suffix = InpSymbolSuffix;
|
|
return true;
|
|
}
|
|
|
|
string probes[] = {"EURUSD","USDJPY","GBPUSD","AUDUSD","USDCAD","USDCHF","NZDUSD"};
|
|
int total = SymbolsTotal(false);
|
|
|
|
for(int p=0; p<ArraySize(probes); ++p)
|
|
{
|
|
int base_len = (int)StringLen(probes[p]);
|
|
for(int i=0; i<total; ++i)
|
|
{
|
|
string nm = SymbolName(i, false);
|
|
if(StringLen(nm) < base_len) continue;
|
|
if(StringSubstr(nm, 0, base_len) == probes[p])
|
|
{
|
|
string suf = StringSubstr(nm, base_len);
|
|
|
|
out_suffix = suf;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
out_suffix = "";
|
|
return false;
|
|
}
|
|
|
|
string ResolveSymbolFromBase(const string base)
|
|
{
|
|
|
|
if(SymbolSelect(base, true)) return base;
|
|
|
|
if(g_symbol_suffix != "")
|
|
{
|
|
string s2 = base + g_symbol_suffix;
|
|
if(SymbolSelect(s2, true)) return s2;
|
|
}
|
|
|
|
int total = SymbolsTotal(false);
|
|
int base_len = (int)StringLen(base);
|
|
for(int i=0; i<total; ++i)
|
|
{
|
|
string nm = SymbolName(i, false);
|
|
if(StringLen(nm) >= base_len && StringSubstr(nm, 0, base_len) == base)
|
|
{
|
|
if(SymbolSelect(nm, true)) return nm;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
#ifndef __PAIR_SPREAD_SUITE__
|
|
#define __PAIR_SPREAD_SUITE__
|
|
|
|
bool HedgeBeta(const double &x[], const double &y[], int n, double &beta)
|
|
{
|
|
if(n < 20) return false;
|
|
double mx = ArrayMean(x), my = ArrayMean(y), num=0, den=0;
|
|
for(int i=0;i<n;i++){ double dx=x[i]-mx, dy=y[i]-my; num+=dx*dy; den+=dy*dy; }
|
|
if(den <= 0) return false;
|
|
beta = num/den;
|
|
return true;
|
|
}
|
|
|
|
|
|
double ZScoreSeriesLatest(const double &arr[])
|
|
{
|
|
int n=ArraySize(arr); if(n<10) return 0.0;
|
|
double m=0,v=0; for(int i=0;i<n;i++) m+=arr[i]; m/=n;
|
|
for(int i=0;i<n;i++){ double d=arr[i]-m; v+=d*d; }
|
|
double sd=(n>1)?MathSqrt(v/(n-1)):0.0; if(sd==0) return 0.0;
|
|
return (arr[0]-m)/sd;
|
|
}
|
|
|
|
bool PairSignalBySpread(int idx, double &z, ENUM_SIGNAL_TYPE &sig)
|
|
{
|
|
sig = SIGNAL_NONE; z = 0.0;
|
|
if(idx<0 || idx>=TOTAL_PAIRS) return false;
|
|
|
|
string s1=g_corr_data[idx].symbol1, s2=g_corr_data[idx].symbol2;
|
|
double p1[], p2[]; if(!GetPricesFromCache(s1,p1) || !GetPricesFromCache(s2,p2)) return false;
|
|
|
|
int n = MathMin(ArraySize(p1), ArraySize(p2));
|
|
if(n < 60) return false;
|
|
|
|
|
|
double l1[], l2[]; ArrayResize(l1,n); ArrayResize(l2,n);
|
|
for(int i=0;i<n;i++){ l1[i]=MathLog(p1[i]); l2[i]=MathLog(p2[i]); }
|
|
|
|
double beta; if(!HedgeBeta(l1,l2,n,beta)) return false;
|
|
|
|
double spr[]; ArrayResize(spr,n);
|
|
for(int i=0;i<n;i++) spr[i] = l1[i] - beta*l2[i];
|
|
|
|
z = ZScoreSeriesLatest(spr);
|
|
|
|
|
|
if(z >= InpZSpikeEnter) { sig = SIGNAL_SB; return true; }
|
|
|
|
if(z <= -InpZSpikeEnter) { sig = SIGNAL_BS; return true; }
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
void ApplySuffixToPairs()
|
|
{
|
|
for(int i=0; i<TOTAL_PAIRS; ++i)
|
|
{
|
|
string s1 = ResolveSymbolFromBase(g_pairs[i][0]);
|
|
string s2 = ResolveSymbolFromBase(g_pairs[i][1]);
|
|
if(s1!="") g_pairs[i][0] = s1;
|
|
if(s2!="") g_pairs[i][1] = s2;
|
|
}
|
|
}
|
|
|
|
double BuildSLTP(const string sym, const bool isBuy, const double atrMult, double &sl, double &tp, const double RR)
|
|
{
|
|
sl = 0.0; tp = 0.0;
|
|
int h = iATR(sym, g_current_timeframe, InpATR_Period);
|
|
if(h == INVALID_HANDLE) return 0.0;
|
|
|
|
double buff[];
|
|
if(CopyBuffer(h, 0, 0, 2, buff) < 2) return 0.0;
|
|
|
|
double atr = buff[1];
|
|
if(atr <= 0) return 0.0;
|
|
|
|
double pt = atr * atrMult;
|
|
double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
|
|
double bid = SymbolInfoDouble(sym, SYMBOL_BID);
|
|
|
|
if(isBuy) { sl = ask - pt; tp = ask + pt * RR; }
|
|
else { sl = bid + pt; tp = bid - pt * RR; }
|
|
|
|
return pt;
|
|
}
|
|
|
|
double ZscoreArray(const double &arr[]){
|
|
int n=ArraySize(arr); if(n<5) return 0.0;
|
|
double m=0, v=0; for(int i=0;i<n;i++) m+=arr[i]; m/=n;
|
|
for(int i=0;i<n;i++){ double d=arr[i]-m; v+=d*d; }
|
|
double sd=(n>1)?MathSqrt(v/(n-1)):0; if(sd==0) return 0.0;
|
|
return (arr[n-1]-m)/sd;
|
|
}
|
|
|
|
bool PassesPreTradeFilters(const string sym)
|
|
{
|
|
if(!InpUsePreFilters) return true;
|
|
|
|
|
|
MqlTick tick; if(!SymbolInfoTick(sym, tick)) return false;
|
|
double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
|
|
double spreadPts = (tick.ask - tick.bid) / pt;
|
|
if(spreadPts > GetSpreadLimit(sym)) return false;
|
|
|
|
|
|
int h = iATR(sym, g_current_timeframe, InpATR_Period);
|
|
double b[]; if(h==INVALID_HANDLE || CopyBuffer(h,0,0,2,b)<2) return false;
|
|
if(b[1] / pt < InpMinATRPoints) return false;
|
|
if(InpRequireHAFreshFlip)
|
|
{
|
|
|
|
int ha = MakeHA(sym, g_current_timeframe);
|
|
double o[], c[]; if(ha==INVALID_HANDLE) return false;
|
|
if(CopyBuffer(ha,0,0,InpHAFreshBars+1,o)<InpHAFreshBars+1) return false;
|
|
if(CopyBuffer(ha,3,0,InpHAFreshBars+1,c)<InpHAFreshBars+1) return false;
|
|
bool flipped=false;
|
|
for(int i=0;i<InpHAFreshBars;i++)
|
|
{
|
|
bool up = c[i]>o[i], upPrev=c[i+1]>o[i+1];
|
|
if(up!=upPrev){ flipped=true; break; }
|
|
}
|
|
if(!flipped) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PreflightDeal(const string sym, ENUM_ORDER_TYPE typ, double vol, double &sl, double &tp,
|
|
string &reason, uint &retcodeOut)
|
|
{
|
|
reason=""; retcodeOut=0;
|
|
if(sym==""){ reason="symbol empty"; return false; }
|
|
if(!EnsureSymbolSelected(sym)){ reason="symbol not tradable"; return false; }
|
|
vol = NormalizeVolumeToStep(sym, vol);
|
|
if(vol<=0){ reason="volume<=0 after normalize"; return false; }
|
|
|
|
if(!PassesPreTradeFilters(sym)){ reason="pre-filter failed"; return false; }
|
|
|
|
double price = (typ==ORDER_TYPE_BUY)? SymbolInfoDouble(sym, SYMBOL_ASK)
|
|
: SymbolInfoDouble(sym, SYMBOL_BID);
|
|
if(price<=0){ reason="no market price"; return false; }
|
|
if(sl>0 || tp>0) EnsureStopsRespectLevels(sym, (typ==ORDER_TYPE_BUY), sl, tp);
|
|
double mg=0; if(!OrderCalcMargin(typ, sym, vol, price, mg)) mg=0;
|
|
const double fm = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
|
if(fm < mg){ reason=StringFormat("insufficient margin need=%.2f free=%.2f", mg, fm); return false; }
|
|
retcodeOut = 0;
|
|
return true;
|
|
}
|
|
|
|
double GetSpreadLimit(const string sym)
|
|
{
|
|
if(StringSubstr(sym,0,3)=="XAU") return MathMax(InpMaxSpreadPoints, 200);
|
|
return InpMaxSpreadPoints;
|
|
}
|
|
|
|
bool SingleSymbolDirection(const string sym, ENUM_SIGNAL_TYPE &out_action)
|
|
{
|
|
|
|
int adx = iADX(sym, g_current_timeframe, InpADX_Period);
|
|
double a[]; if(adx==INVALID_HANDLE || CopyBuffer(adx,2,0,2,a)<2) return false;
|
|
if(a[1] < InpADX_TrendLevel) return false;
|
|
|
|
int ha = MakeHA(sym, g_current_timeframe);
|
|
double o[], c[]; if(ha==INVALID_HANDLE || CopyBuffer(ha,0,0,1,o)<1 || CopyBuffer(ha,3,0,1,c)<1) return false;
|
|
bool bull = (c[0] > o[0]);
|
|
bool bear = (c[0] < o[0]);
|
|
|
|
if(g_use_mtf_filter){
|
|
int hac = MakeHA(sym, InpTimeframe_Confirmation);
|
|
double oc[], cc[]; if(hac==INVALID_HANDLE || CopyBuffer(hac,0,0,1,oc)<1 || CopyBuffer(hac,3,0,1,cc)<1) return false;
|
|
if(bull && !(cc[0] > oc[0])) return false;
|
|
if(bear && !(cc[0] < oc[0])) return false;
|
|
}
|
|
|
|
if(bull) out_action = SIGNAL_BS;
|
|
else if(bear) out_action = SIGNAL_SB;
|
|
else return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
double PipValuePerLot(const string sym)
|
|
{
|
|
double tv = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
|
|
double ts = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
|
|
int dg = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
|
|
if(tv<=0 || ts<=0) return 0.0;
|
|
|
|
double pip_points = (dg==5 || dg==3) ? 10.0 : 1.0;
|
|
return tv * pip_points;
|
|
}
|
|
|
|
void AdjustLotsRiskParity(const string sym1, const string sym2, double &lot1, double &lot2, bool allowUpsize=false)
|
|
{
|
|
double pv1=PipValuePerLot(sym1), pv2=PipValuePerLot(sym2);
|
|
if(pv1<=0 || pv2<=0){ lot2=lot1; return; }
|
|
|
|
double step1=SymbolInfoDouble(sym1,SYMBOL_VOLUME_STEP);
|
|
double step2=SymbolInfoDouble(sym2,SYMBOL_VOLUME_STEP);
|
|
double min1 =SymbolInfoDouble(sym1,SYMBOL_VOLUME_MIN);
|
|
double max1 =SymbolInfoDouble(sym1,SYMBOL_VOLUME_MAX);
|
|
double min2 =SymbolInfoDouble(sym2,SYMBOL_VOLUME_MIN);
|
|
double max2 =SymbolInfoDouble(sym2,SYMBOL_VOLUME_MAX);
|
|
|
|
lot1 = MathMax(min1, MathMin(max1, MathFloor(lot1/step1)*step1));
|
|
|
|
double R = lot1 * pv1;
|
|
double raw2= R / pv2;
|
|
double rounded2 = MathFloor(raw2/step2 + 0.5) * step2;
|
|
if(!allowUpsize && rounded2 > raw2)
|
|
rounded2 = MathFloor(raw2/step2) * step2;
|
|
|
|
lot2 = MathMax(min2, MathMin(max2, rounded2));
|
|
|
|
PrintFormat("[RiskParity] %s pip=$%.2f, %s pip=$%.2f -> lots %.2f / %.2f (target %.5f)",
|
|
sym1, pv1, sym2, pv2, lot1, lot2, raw2);
|
|
}
|
|
|
|
bool InRowCooldown(const int row){
|
|
if(row<0 || row>=TOTAL_PAIRS) return false;
|
|
datetime t0 = g_last_row_close_bar[row];
|
|
if(t0==0) return false;
|
|
|
|
string sym = g_corr_data[row].symbol1;
|
|
int idx = iBarShift(sym, g_current_timeframe, t0, true);
|
|
if(idx < 0) return false;
|
|
return (idx < InpRowCooldownBars);
|
|
}
|
|
|
|
int GetMatrixPixelHeight()
|
|
{
|
|
const int cell = (int)(40 * g_scale);
|
|
const int pad = (int)(10 * g_scale);
|
|
int n = ArraySize(g_matrix_symbols);
|
|
return (n + 1) * cell + pad;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Builds a unique list of symbols for the price cache. |
|
|
//+------------------------------------------------------------------+
|
|
void BuildUniqueSymbolListAndPrepareCache()
|
|
{
|
|
string temp_list[];
|
|
int k = 0;
|
|
for(int i=0; i<TOTAL_PAIRS; i++)
|
|
{
|
|
ArrayResize(temp_list, k+2);
|
|
temp_list[k++] = g_pairs[i][0];
|
|
temp_list[k++] = g_pairs[i][1];
|
|
}
|
|
int nccy = ArraySize(g_matrix_symbols);
|
|
for(int r=0; r<nccy; r++)
|
|
{
|
|
for(int c=r; c<nccy; c++)
|
|
{
|
|
|
|
string s_cross = GetSymbolForCurrencies(g_matrix_symbols[r], g_matrix_symbols[c]);
|
|
if(s_cross != "")
|
|
{
|
|
ArrayResize(temp_list, k+1);
|
|
temp_list[k++] = s_cross;
|
|
}
|
|
|
|
|
|
string s_usd_r = GetSymbolForCurrencies(g_matrix_symbols[r], "USD");
|
|
if(s_usd_r != "") { ArrayResize(temp_list, k+1); temp_list[k++] = s_usd_r; }
|
|
|
|
string s_usd_c = GetSymbolForCurrencies(g_matrix_symbols[c], "USD");
|
|
if(s_usd_c != "") { ArrayResize(temp_list, k+1); temp_list[k++] = s_usd_c; }
|
|
|
|
|
|
string s_eur_r = GetSymbolForCurrencies(g_matrix_symbols[r], "EUR");
|
|
if(s_eur_r != "") { ArrayResize(temp_list, k+1); temp_list[k++] = s_eur_r; }
|
|
|
|
string s_eur_c = GetSymbolForCurrencies(g_matrix_symbols[c], "EUR");
|
|
if(s_eur_c != "") { ArrayResize(temp_list, k+1); temp_list[k++] = s_eur_c; }
|
|
}
|
|
}
|
|
|
|
string unique_symbols[];
|
|
for(int i=0; i<ArraySize(temp_list); i++)
|
|
{
|
|
string s = temp_list[i];
|
|
if(s == "") continue;
|
|
bool seen = false;
|
|
for(int j=0; j<ArraySize(unique_symbols); j++)
|
|
{
|
|
if(unique_symbols[j] == s) { seen = true; break; }
|
|
}
|
|
if(!seen)
|
|
{
|
|
int sz = ArraySize(unique_symbols);
|
|
ArrayResize(unique_symbols, sz+1);
|
|
unique_symbols[sz] = s;
|
|
}
|
|
}
|
|
|
|
int total_unique = ArraySize(unique_symbols);
|
|
ArrayResize(g_price_cache, total_unique);
|
|
ArrayResize(g_last_cache_time, total_unique);
|
|
ArrayInitialize(g_last_cache_time, 0);
|
|
|
|
for(int i=0; i<total_unique; i++)
|
|
{
|
|
g_price_cache[i].name = unique_symbols[i];
|
|
ArrayResize(g_price_cache[i].prices, InpLookback);
|
|
|
|
if(!EnsureSymbolSelected(unique_symbols[i]))
|
|
Print("Warning: cannot select symbol ", unique_symbols[i], " to Market Watch.");
|
|
}
|
|
}
|
|
|
|
|
|
bool GetPricesFromCache(string symbol, double &prices[])
|
|
{
|
|
for(int i = 0; i < ArraySize(g_price_cache); i++)
|
|
{
|
|
if(g_price_cache[i].name == symbol)
|
|
{
|
|
ArrayCopy(prices, g_price_cache[i].prices, 0, 0, WHOLE_ARRAY);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
double CalculatePearsonCorrelation(const double &x[], const double &y[], int count)
|
|
{
|
|
if(count == 0) return 0;
|
|
|
|
double sum_x = 0;
|
|
double sum_y = 0;
|
|
double sum_xy = 0;
|
|
double sum_x2 = 0;
|
|
double sum_y2 = 0;
|
|
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
sum_x += x[i];
|
|
sum_y += y[i];
|
|
sum_xy += x[i] * y[i];
|
|
sum_x2 += x[i] * x[i];
|
|
sum_y2 += y[i] * y[i];
|
|
}
|
|
|
|
double num = count * sum_xy - sum_x * sum_y;
|
|
double den_x = count * sum_x2 - sum_x * sum_x;
|
|
double den_y = count * sum_y2 - sum_y * sum_y;
|
|
|
|
if(den_x <= 0 || den_y <= 0) return 0.0;
|
|
|
|
return num / (MathSqrt(den_x * den_y));
|
|
}
|
|
|
|
double ArraySum(const double &arr[])
|
|
{
|
|
double s = 0;
|
|
for(int i = 0; i < ArraySize(arr); i++)
|
|
s += arr[i];
|
|
return s;
|
|
}
|
|
|
|
double ArrayMean(const double &arr[])
|
|
{
|
|
int s = ArraySize(arr);
|
|
if(s == 0) return 0;
|
|
return ArraySum(arr) / s;
|
|
}
|
|
|
|
void ArrayRemove(double &arr[], int start_index, int count)
|
|
{
|
|
int size = ArraySize(arr);
|
|
if(start_index < 0 || start_index >= size || count <= 0) return;
|
|
|
|
int to_move = size - (start_index + count);
|
|
if(to_move > 0)
|
|
ArrayCopy(arr, arr, start_index, start_index + count, to_move);
|
|
|
|
ArrayResize(arr, size - count);
|
|
}
|
|
|
|
|
|
void CalculateClosedProfitAndCount(double &profit_out, int &count_out)
|
|
{
|
|
profit_out = 0.0;
|
|
count_out = 0;
|
|
HistorySelect((datetime)0, TimeCurrent());
|
|
|
|
CDealInfo deal;
|
|
for(int i = 0; i < HistoryDealsTotal(); i++)
|
|
{
|
|
if(deal.SelectByIndex(i))
|
|
{
|
|
|
|
if(deal.Magic() == InpMagicNumber &&
|
|
(deal.Entry() == DEAL_ENTRY_OUT || deal.Entry() == DEAL_ENTRY_INOUT))
|
|
{
|
|
profit_out += deal.Profit() + deal.Swap() + deal.Commission();
|
|
count_out++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool IsSessionOpen(int open_hour, int close_hour, int current_hour_gmt)
|
|
{
|
|
if (open_hour < close_hour)
|
|
{
|
|
return current_hour_gmt >= open_hour && current_hour_gmt < close_hour;
|
|
}
|
|
else
|
|
{
|
|
return current_hour_gmt >= open_hour || current_hour_gmt < close_hour;
|
|
}
|
|
}
|
|
void CreateCellWithText(
|
|
string name,
|
|
string text,
|
|
int x, int y,
|
|
int w, int h,
|
|
color bg_clr,
|
|
color text_clr,
|
|
int font_size,
|
|
ENUM_ANCHOR_POINT anchor,
|
|
string font="Arial",
|
|
ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER
|
|
)
|
|
{
|
|
CreateRectangle(name + "_BG", x, y, w, h, bg_clr, corner, BORDER_FLAT);
|
|
int label_x = (anchor == ANCHOR_CENTER) ? (x + w / 2) : (x + 5);
|
|
CreateLabel(name + "_TXT", text, label_x, y + h / 2 - font_size / 2 + 3, text_clr, anchor, font, font_size, corner);
|
|
}
|
|
|
|
void CreateRectangle(
|
|
string name,
|
|
int x, int y,
|
|
int w, int h,
|
|
color clr,
|
|
ENUM_BASE_CORNER corner,
|
|
ENUM_BORDER_TYPE border=BORDER_FLAT,
|
|
bool is_background=true
|
|
)
|
|
{
|
|
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
|
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
|
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
|
|
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clr);
|
|
ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
|
|
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, border);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
|
|
if (is_background)
|
|
ObjectSetInteger(0, name, OBJPROP_BACK, true);
|
|
}
|
|
|
|
void CreateLabel(
|
|
string name,
|
|
string text,
|
|
int x, int y,
|
|
color clr,
|
|
ENUM_ANCHOR_POINT anchor=ANCHOR_LEFT,
|
|
string font="Arial",
|
|
int font_size=-1,
|
|
ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER
|
|
)
|
|
{
|
|
if (font_size == -1)
|
|
font_size = g_curr_font_size; // ใช้ Dynamic Font
|
|
|
|
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetString(0, name, OBJPROP_TEXT, text);
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
|
|
ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
|
|
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
|
|
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_size);
|
|
ObjectSetString(0, name, OBJPROP_FONT, font);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
|
|
}
|
|
|
|
void CreateButton(
|
|
string name,
|
|
string text,
|
|
int x, int y,
|
|
int w, int h,
|
|
color bg_clr,
|
|
ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER
|
|
)
|
|
{
|
|
ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
|
|
ObjectSetString(0, name, OBJPROP_TEXT, text);
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
|
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
|
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
|
|
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg_clr);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, clrWhite);
|
|
ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
|
|
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, g_curr_font_size); // ใช้ Dynamic Font
|
|
ObjectSetInteger(0, name, OBJPROP_STATE, false);
|
|
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, C'80,80,80');
|
|
}
|
|
|
|
void CreateEditBox(
|
|
string name,
|
|
string text,
|
|
int x, int y,
|
|
int w, int h,
|
|
ENUM_ALIGN_MODE align=ALIGN_RIGHT
|
|
)
|
|
{
|
|
ObjectCreate(0, name, OBJ_EDIT, 0, 0, 0);
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
|
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
|
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
|
|
ObjectSetString(0, name, OBJPROP_TEXT, text);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, c_font_color);
|
|
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, c_edit_bg);
|
|
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, c_edit_border);
|
|
ObjectSetInteger(0, name, OBJPROP_ALIGN, align);
|
|
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, g_curr_font_size); // ใช้ Dynamic Font
|
|
ObjectSetString(0, name, OBJPROP_FONT, "Arial");
|
|
}
|
|
|
|
void UpdateAutoButton(int index)
|
|
{
|
|
string btn_name = g_object_prefix + "AUTO_BTN_" + (string)index;
|
|
|
|
if (g_auto_status[index]) {
|
|
ObjectSetString(0, btn_name, OBJPROP_TEXT, "on");
|
|
ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, c_btn_on_bg);
|
|
} else {
|
|
ObjectSetString(0, btn_name, OBJPROP_TEXT, "off");
|
|
ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, c_btn_off_bg);
|
|
}
|
|
}
|
|
|
|
color InterpolateColor(color start_color, color end_color, double factor)
|
|
{
|
|
if (factor < 0) factor = 0;
|
|
if (factor > 1) factor = 1;
|
|
|
|
uchar start_r = (uchar)((start_color >> 16) & 0xFF);
|
|
uchar start_g = (uchar)((start_color >> 8) & 0xFF);
|
|
uchar start_b = (uchar)(start_color & 0xFF);
|
|
|
|
uchar end_r = (uchar)((end_color >> 16) & 0xFF);
|
|
uchar end_g = (uchar)((end_color >> 8) & 0xFF);
|
|
uchar end_b = (uchar)(end_color & 0xFF);
|
|
|
|
uchar r = (uchar)(start_r + (end_r - start_r) * factor);
|
|
uchar g = (uchar)(start_g + (end_g - start_g) * factor);
|
|
uchar b = (uchar)(start_b + (end_b - start_b) * factor);
|
|
|
|
return StringToColor((string)r + "," + (string)g + "," + (string)b);
|
|
}
|
|
|
|
string GetSymbolForCurrencies(string ccy1, string ccy2)
|
|
{
|
|
if (ccy1 == ccy2)
|
|
return "";
|
|
|
|
string base = ccy1 + ccy2;
|
|
string real1 = ResolveSymbolFromBase(base);
|
|
if (real1 != "")
|
|
return real1;
|
|
|
|
base = ccy2 + ccy1;
|
|
string real2 = ResolveSymbolFromBase(base);
|
|
if (real2 != "")
|
|
return real2;
|
|
|
|
return "";
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Function: Update Dynamic Layout (Call this on Init & Resize) |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateDynamicLayout()
|
|
{
|
|
|
|
if (InpAutoResize)
|
|
{
|
|
long chart_h = ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
|
|
|
|
g_scale = (double)chart_h / 900.0;
|
|
|
|
|
|
if(g_scale < 0.75) g_scale = 0.75;
|
|
if(g_scale > 1.6) g_scale = 1.6;
|
|
}
|
|
else
|
|
{
|
|
g_scale = InpManualScale;
|
|
}
|
|
|
|
|
|
g_curr_font_size = (int)(InpBaseFontSize * g_scale);
|
|
if (g_curr_font_size < 6) g_curr_font_size = 6;
|
|
|
|
|
|
c_row_h = (int)(BASE_ROW_H * g_scale);
|
|
c_margin = (int)(BASE_MARGIN * g_scale);
|
|
|
|
|
|
c_col_close_w = (int)(40 * g_scale);
|
|
c_col_profit_w = (int)(65 * g_scale);
|
|
c_col_order_w = (int)(40 * g_scale);
|
|
c_col_entry_buy_w = (int)(60 * g_scale);
|
|
c_col_auto_w = (int)(40 * g_scale);
|
|
c_col_entry_sell_w = (int)(60 * g_scale);
|
|
c_col_corr_w = (int)(160 * g_scale);
|
|
c_col_current_corr_w = (int)(55 * g_scale);
|
|
c_col_delta_corr_w = (int)(55 * g_scale);
|
|
c_col_sprd_div_w = (int)(80 * g_scale);
|
|
c_col_confidence_w = (int)(60 * g_scale);
|
|
c_info_panel_w = (int)(165 * g_scale);
|
|
|
|
|
|
int gap = (int)(2 * g_scale);
|
|
|
|
c_col_close_x = (int)(15 * g_scale);
|
|
c_col_profit_x = c_col_close_x + c_col_close_w + gap;
|
|
c_col_order_x = c_col_profit_x + c_col_profit_w + gap;
|
|
c_col_entry_buy_x = c_col_order_x + c_col_order_w + gap;
|
|
c_col_auto_x = c_col_entry_buy_x + c_col_entry_buy_w + gap;
|
|
c_col_entry_sell_x = c_col_auto_x + c_col_auto_w + gap;
|
|
c_col_corr_x = c_col_entry_sell_x + c_col_entry_sell_w + gap;
|
|
c_col_current_corr_x = c_col_corr_x + c_col_corr_w + gap;
|
|
c_col_delta_corr_x = c_col_current_corr_x + c_col_current_corr_w + gap;
|
|
c_col_sprd_div_x = c_col_delta_corr_x + c_col_delta_corr_w + gap;
|
|
c_col_confidence_x = c_col_sprd_div_x + c_col_sprd_div_w + gap;
|
|
|
|
c_main_panel_w = c_col_confidence_x + c_col_confidence_w + (int)(5 * g_scale);
|
|
c_info_panel_x = c_main_panel_w + (int)(10 * g_scale);
|
|
c_total_panel_w = c_info_panel_x + c_info_panel_w;
|
|
} |