From f6c80bd5fcabf26937097803233171ec8edcd175 Mon Sep 17 00:00:00 2001 From: Toh4iem9 Date: Fri, 6 Feb 2026 12:25:40 +0100 Subject: [PATCH] refactor(scripts): Global Sentiment on H1, M15, M5 --- Scripts/MyScripts/Market_Scanner_Pro.mq5 | 337 +++++++++++++++-------- 1 file changed, 225 insertions(+), 112 deletions(-) diff --git a/Scripts/MyScripts/Market_Scanner_Pro.mq5 b/Scripts/MyScripts/Market_Scanner_Pro.mq5 index 54d026e..f2f06a4 100644 --- a/Scripts/MyScripts/Market_Scanner_Pro.mq5 +++ b/Scripts/MyScripts/Market_Scanner_Pro.mq5 @@ -1,12 +1,12 @@ //+------------------------------------------------------------------+ //| Market_Scanner_Pro.mq5 | -//| QuantScan 4.2 - Benchmark Exclusions | +//| QuantScan 5.1 - Multi-TF Global Sentiment | //| Copyright 2026, xxxxxxxx | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, xxxxxxxx" -#property version "4.20" // Logic update: Exclude Benchmarks from Stats -#property description "Exports 'QuantScan 4.0' for LLM Analysis." -#property description "Includes Trend, Volatility, Stats + Beta/Alpha metrics." +#property version "5.10" // Global Sentiment on H1, M15, M5 +#property description "Exports 'QuantScan 5.0' for LLM Analysis." +#property description "3-Layer Logic & Multi-TF Risk Sentiment." #property script_show_inputs //--- Include Custom Calculators @@ -35,9 +35,13 @@ input int InpScanHistory = 500; input group "Benchmark Settings" input int InpBetaLookback = 60; -input group "Timeframes" -input ENUM_TIMEFRAMES InpTFFast = PERIOD_M15; -input ENUM_TIMEFRAMES InpTFSlow = PERIOD_H1; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +input group "Timeframes (3-Layer Model)" +input ENUM_TIMEFRAMES InpTFSlow = PERIOD_H1; // Layer 1: Context/Sentiment Base +input ENUM_TIMEFRAMES InpTFMiddle = PERIOD_M15; // Layer 2: Flow/Session Sentiment +input ENUM_TIMEFRAMES InpTFFast = PERIOD_M5; // Layer 3: Trigger/Shock Sentiment input group "Metric Settings" input int InpDSMAPeriod = 40; @@ -66,23 +70,27 @@ struct QuantData string symbol; double price; - // --- H1 --- + // --- Layer 1: H1 Context --- double trend_score; double trend_qual; string zone; - - // Strings for Stats to allow "BENCHMARK" text string rel_strength_str; string beta_str; string alpha_str; - // --- M15 --- - double momentum; - double vol_qual; - string squeeze; - double z_score; - double vola_regime; - string tsi_dir; + // --- Layer 2: M15 Flow --- + double m15_momentum; + double m15_vol_qual; + string m15_squeeze; + double m15_z_score; + double m15_vola_regime; + string m15_tsi_dir; + + // --- Layer 3: M5 Trigger --- + double m5_momentum; + double m5_vol_qual; + string m5_tsi_dir; + double m5_velocity; // --- Composites --- double rev_prob; @@ -94,31 +102,65 @@ struct QuantData //+------------------------------------------------------------------+ bool IsForexPair(string sym) { -// Safety: If symbol IS one of the benchmarks, we don't classify it as generic forex pair here if(sym == InpBenchmark || sym == InpForexBench) return false; - if(StringFind(sym, "USD") != -1 || StringFind(sym, "EUR") != -1 || - StringFind(sym, "GBP") != -1 || StringFind(sym, "JPY") != -1 || - StringFind(sym, "CHF") != -1 || StringFind(sym, "AUD") != -1 || - StringFind(sym, "CAD") != -1 || StringFind(sym, "NZD") != -1 || - StringFind(sym, "XAU") != -1 || StringFind(sym, "XAG") != -1) + StringFind(sym, "JPY") != -1 || StringFind(sym, "CHF") != -1 || + StringFind(sym, "AUD") != -1 || StringFind(sym, "CAD") != -1 || StringFind(sym, "NZD") != -1) { - if(StringFind(sym, "XTI") != -1) - return false; - if(StringFind(sym, "UKO") != -1) - return false; - if(StringFind(sym, "USO") != -1) - return false; - if(StringFind(sym, "BTC") != -1) - return false; - if(StringFind(sym, "ETH") != -1) + if(StringFind(sym, "XAU")!=-1 || StringFind(sym, "XTI")!=-1 || StringFind(sym, "WTI")!=-1 || StringFind(sym, "BTC")!=-1 || StringFind(sym, "ETH")!=-1) return false; return true; } return false; } +//+------------------------------------------------------------------+ +//| Helper: Get Sentiment String for TF | +//+------------------------------------------------------------------+ +string GetSentimentForTF(ENUM_TIMEFRAMES tf) + { +// Uses Last Closed Bar change vs Prev + double u_close[2], d_close[2]; + +// Fetch 2 bars. Index 0=Oldest (Prev), Index 1=Newest (Last Closed) +// Note: If using FetchData logic (ArraySetAsSeries false), copy from end. +// But CopyClose(..., 0, 2) returns: [0]=Bar 1 ago, [1]=Bar 0 (Current) ? +// Docs: CopyClose(..., start_pos, count, buffer) -> start_pos relative to current. +// start_pos=0 is current bar. start_pos=1 is closed bar. +// Let's create array of 2 elements from start_pos=1 (last closed two candles). +// So [0] = Bar 2, [1] = Bar 1. + + if(CopyClose(InpBenchmark, tf, 1, 2, u_close) != 2) + return "N/A"; + if(CopyClose(InpForexBench, tf, 1, 2, d_close) != 2) + return "N/A"; + + double us500_chg = (u_close[1] - u_close[0]); + double dxy_chg = (d_close[1] - d_close[0]); + + double us500_pct = (u_close[0]!=0) ? (us500_chg / u_close[0])*100 : 0; + double dxy_pct = (d_close[0]!=0) ? (dxy_chg / d_close[0])*100 : 0; + + string state = "MIXED"; + if(dxy_chg < 0 && us500_chg > 0) + state = "RISK-ON"; + else + if(dxy_chg > 0 && us500_chg < 0) + state = "RISK-OFF"; + else + if(dxy_chg > 0 && us500_chg > 0) + state = "STRESS"; + else + if(dxy_chg < 0 && us500_chg < 0) + state = "DEFLATION"; + +// Format: "RISK-ON (S: +0.2% D: -0.1%)" + string tf_name = EnumToString(tf); + StringReplace(tf_name, "PERIOD_", ""); + return StringFormat("%s: %s (US:%.2f%% DX:%.2f%%)", tf_name, state, us500_pct, dxy_pct); + } + //+------------------------------------------------------------------+ //| Script Start | //+------------------------------------------------------------------+ @@ -141,14 +183,35 @@ void OnStart() total_symbols = StringSplit(InpSymbolList, u_sep, symbols); } - double bench_change_pct = 0.0; - bool bench_global_ready = SymbolSelect(InpBenchmark, true); - bool bench_forex_ready = SymbolSelect(InpForexBench, true); +// --- Global Sentiment Analysis (Multi-TF) --- + string sentiment_line = "### GLOBAL_SENTIMENT | "; - if(!bench_global_ready) - Print("Warning: Global Benchmark '", InpBenchmark, "' not found."); - if(!bench_forex_ready) - Print("Warning: Forex Benchmark '", InpForexBench, "' not found."); + bool has_us500 = SymbolSelect(InpBenchmark, true); + bool has_dxy = SymbolSelect(InpForexBench, true); + + if(has_us500 && has_dxy) + { + string s1 = GetSentimentForTF(InpTFSlow); + string s2 = GetSentimentForTF(InpTFMiddle); + string s3 = GetSentimentForTF(InpTFFast); + + sentiment_line += s1 + " | " + s2 + " | " + s3 + " ###"; + } + else + { + sentiment_line += "Benchmarks Missing (Check High/Low settings) ###"; + } + +// --- Benchmark for RS (H1 Context) --- + double bench_change_pct = 0.0; + if(has_us500) + { + double b_close[], b_open[]; + // Using H1 for RS base + if(CopyClose(InpBenchmark, InpTFSlow, 1, 1, b_close) > 0 && CopyOpen(InpBenchmark, InpTFSlow, InpRSBars, 1, b_open) > 0) + if(b_open[0] != 0) + bench_change_pct = ((b_close[0] - b_open[0]) / b_open[0]) * 100.0; + } string filename = "QuantScan_" + TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES) + ".csv"; StringReplace(filename, ":", ""); @@ -158,10 +221,15 @@ void OnStart() if(file_handle == INVALID_HANDLE) return; -// --- DYNAMIC HEADER GENERATION --- +// --- WRITE HEADER --- + FileWrite(file_handle, sentiment_line); + +// --- DYNAMIC COLUMNS --- string str_slow = EnumToString(InpTFSlow); - string str_fast = EnumToString(InpTFFast); StringReplace(str_slow, "PERIOD_", ""); + string str_mid = EnumToString(InpTFMiddle); + StringReplace(str_mid, "PERIOD_", ""); + string str_fast = EnumToString(InpTFFast); StringReplace(str_fast, "PERIOD_", ""); string header = ""; @@ -169,7 +237,7 @@ void OnStart() header += "SYMBOL;"; header += "PRICE;"; -// Context (Slow) +// Layer 1 header += StringFormat("TREND_SCORE_%s;", str_slow); header += StringFormat("TREND_QUAL_%s;", str_slow); header += StringFormat("ZONE_%s;", str_slow); @@ -177,13 +245,19 @@ void OnStart() header += StringFormat("BETA_%s;", str_slow); header += StringFormat("ALPHA_%s;", str_slow); -// Trigger (Fast) +// Layer 2 + header += StringFormat("MOMENTUM_%s;", str_mid); + header += StringFormat("VOL_QUAL_%s;", str_mid); + header += StringFormat("SQUEEZE_%s;", str_mid); + header += StringFormat("Z_SCORE_%s;", str_mid); + header += StringFormat("VOL_REGIME_%s;", str_mid); + header += StringFormat("TSI_DIR_%s;", str_mid); + +// Layer 3 header += StringFormat("MOMENTUM_%s;", str_fast); header += StringFormat("VOL_QUAL_%s;", str_fast); - header += StringFormat("SQUEEZE_%s;", str_fast); - header += StringFormat("Z_SCORE_%s;", str_fast); - header += StringFormat("VOL_REGIME_%s;", str_fast); header += StringFormat("TSI_DIR_%s;", str_fast); + header += StringFormat("VELOCITY_%s;", str_fast); // Composites header += "REVERSION_PROB;"; @@ -201,25 +275,32 @@ void OnStart() QuantData data; ZeroMemory(data); - if(RunQuantAnalysis(sym, data)) + if(RunQuantAnalysis(sym, bench_change_pct, data)) { - // Note: Strings used for stats FileWrite(file_handle, data.timestamp, data.symbol, DoubleToString(data.price, (int)SymbolInfoInteger(sym, SYMBOL_DIGITS)), + // Layer 1 DoubleToString(data.trend_score, 2), DoubleToString(data.trend_qual, 2), data.zone, data.rel_strength_str, data.beta_str, data.alpha_str, - DoubleToString(data.momentum, 2), - DoubleToString(data.vol_qual, 2), - data.squeeze, - DoubleToString(data.z_score, 2), - DoubleToString(data.vola_regime, 2), - data.tsi_dir, + // Layer 2 + DoubleToString(data.m15_momentum, 2), + DoubleToString(data.m15_vol_qual, 2), + data.m15_squeeze, + DoubleToString(data.m15_z_score, 2), + DoubleToString(data.m15_vola_regime, 2), + data.m15_tsi_dir, + // Layer 3 + DoubleToString(data.m5_momentum, 2), + DoubleToString(data.m5_vol_qual, 2), + data.m5_tsi_dir, + DoubleToString(data.m5_velocity, 2), + // Composites DoubleToString(data.rev_prob, 0) + "%", data.absorption ); @@ -232,7 +313,7 @@ void OnStart() //+------------------------------------------------------------------+ //| Core Logic | //+------------------------------------------------------------------+ -bool RunQuantAnalysis(string sym, QuantData &data) +bool RunQuantAnalysis(string sym, double bench_change, QuantData &data) { data.timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES); StringReplace(data.timestamp, ".", "."); @@ -240,30 +321,29 @@ bool RunQuantAnalysis(string sym, QuantData &data) data.price = SymbolInfoDouble(sym, SYMBOL_BID); // ================================================================= -// PHASE 1: H1 CONTEXT +// LAYER 1: CONTEXT (H1) // ================================================================= - double h1_o[], h1_h[], h1_l[], h1_c[]; - long h1_v[]; - datetime h1_t[]; - if(!FetchData(sym, InpTFSlow, InpScanHistory, h1_t, h1_o, h1_h, h1_l, h1_c, h1_v)) + double slow_o[], slow_h[], slow_l[], slow_c[]; + long slow_v[]; + datetime slow_t[]; + if(!FetchData(sym, InpTFSlow, InpScanHistory, slow_t, slow_o, slow_h, slow_l, slow_c, slow_v)) return false; - double h1_atr = Calc_ATR(h1_o, h1_h, h1_l, h1_c, InpATRPeriod); - if(h1_atr == 0) + double slow_atr = Calc_ATR(slow_o, slow_h, slow_l, slow_c, InpATRPeriod); + if(slow_atr == 0) return false; - data.trend_score = Calc_DSMA_Score(h1_o, h1_h, h1_l, h1_c, h1_atr); - data.trend_qual = Calc_ER(h1_o, h1_h, h1_l, h1_c, InpERPeriod); + data.trend_score = Calc_DSMA_Score(slow_o, slow_h, slow_l, slow_c, slow_atr); + data.trend_qual = Calc_ER(slow_o, slow_h, slow_l, slow_c, InpERPeriod); data.zone = Calc_MurreyZone(sym, InpTFSlow); -// --- BETA / ALPHA Calculation (With Exclusion Logic) --- +// Benchmark Stats (Beta/Alpha) bool is_benchmark = (sym == InpBenchmark || sym == InpForexBench); - if(is_benchmark) { - data.rel_strength_str = "BENCHMARK"; - data.beta_str = "1.00"; - data.alpha_str = "0.00"; + data.rel_strength_str="BENCH"; + data.beta_str="1.0"; + data.alpha_str="0.0"; } else { @@ -276,26 +356,23 @@ bool RunQuantAnalysis(string sym, QuantData &data) { CMathStatisticsCalculator stats; double asset_ret[], bench_ret[]; - - int h1_size = ArraySize(h1_c); - double asset_subset[]; - ArrayResize(asset_subset, InpBetaLookback); - double bench_subset[]; - ArrayResize(bench_subset, InpBetaLookback); + int size = ArraySize(slow_c); + double asset_sub[], bench_sub[]; + ArrayResize(asset_sub, InpBetaLookback); + ArrayResize(bench_sub, InpBetaLookback); for(int k=0; k 3.0) + if(MathAbs(data.m15_z_score) > 3.0) score += 40; else - if(MathAbs(data.z_score) > 2.0) + if(MathAbs(data.m15_z_score) > 2.0) score += 20; if(StringFind(data.zone, "Extreme") >= 0) score += 30; - if(data.momentum > 0.90 || data.momentum < 0.10) + if(data.m15_momentum > 0.90 || data.m15_momentum < 0.10) score += 30; data.rev_prob = score; - int idx_cl = ArraySize(m15_c) - 2; - if(idx_cl >= 0 && m15_atr > 0) +// Absorption based on Flow (M15) or Trig (M5)? Standard is Flow due to volume significance. +// Let's stick to M15 for Absorption to filter M5 noise. + int idx_cl = ArraySize(mid_c) - 2; + if(idx_cl >= 0 && mid_atr > 0) { - double body = MathAbs(m15_c[idx_cl] - m15_o[idx_cl]); - CRelativeVolumeCalculator rv_calc; - rv_calc.Init(InpRVOLPeriod); - double bar_rvol = rv_calc.CalculateSingle(ArraySize(m15_v), m15_v, idx_cl); - - if(bar_rvol > 2.0 && body < (0.4 * m15_atr)) + double body = MathAbs(mid_c[idx_cl] - mid_o[idx_cl]); + CRelativeVolumeCalculator rv; + rv.Init(InpRVOLPeriod); + double bar_rvol = rv.CalculateSingle(ArraySize(mid_v), mid_v, idx_cl); + if(bar_rvol > 2.0 && body < (0.4 * mid_atr)) data.absorption = "YES"; else data.absorption = "NO"; @@ -370,6 +462,28 @@ bool RunQuantAnalysis(string sym, QuantData &data) return true; } +//+------------------------------------------------------------------+ +//| Velocity Calculation | +//+------------------------------------------------------------------+ +double Calc_Velocity(const double &close[], double atr, int period) + { + if(atr == 0) + return 0; + int total = ArraySize(close); + if(total <= period+2) + return 0; + + double sum_move = 0; + for(int i=0; i