//+------------------------------------------------------------------+ //| CrossAsset.mqh | //| AnimateDread | //| | //| Information that is NOT a transform of the traded symbol's own | //| OHLCV series: what every OTHER instrument was doing at the same | //| moment. | //| | //| Motivation. Every feature the network sees today is some function | //| of one price series - returns, ranges, oscillators, cloud | //| distances, swing structure. Measured end to end, that family | //| carries no directional edge (research/test_classic.py, and the | //| mutual-information floor before it). A single series simply may | //| not contain the answer. EURUSD moving is a statement about EUR | //| and about USD, and which one moved is invisible from EURUSD | //| alone - but it is plainly visible if you also look at EURJPY, | //| GBPUSD and the rest. | //| | //| So this builds a CURRENCY STRENGTH INDEX. For each currency, the | //| index is the average log-return across every available pair | //| containing it, signed so that "up" always means that currency | //| strengthened. The traded symbol's own base and quote indices, | //| and the DIVERGENCE between the pair and what its two currencies | //| were separately doing, are the features. | //| | //| INDEX MODE (base == quote). A CFD on an index or a commodity | //| reports the same currency on both sides (SP500 -> USD/USD), so | //| the FX encoding above degenerates: base and quote strength are | //| the SAME series twice, and the divergence collapses to the | //| symbol's own return - three of six slots wasted. For those | //| symbols the panel re-encodes: the DENOMINATION currency's | //| strength (a stronger USD mechanically pressures a USD-priced | //| index), a RISK-PROXY currency's strength (JPY by preference - | //| yen strength is the classic FX risk-off tell for equities), and | //| divergence becomes the symbol's own move minus what the | //| denomination currency alone implies. Same six slots, no width | //| change; the fingerprint carries an :IDX2 tag so index-symbol | //| models trained under the old degenerate encoding re-key. | //| | //| Cost discipline: the whole cross-asset panel is built ONCE per | //| training run into arrays indexed by the traded symbol's own bar | //| index. Calling iBarShift()/CopyClose() per bar per pair would be | //| ~8 currencies x N pairs x 178k bars of cross-symbol lookups. | //+------------------------------------------------------------------+ #ifndef WARRIOR_CROSSASSET_MQH #define WARRIOR_CROSSASSET_MQH #define CROSSASSET_MAX_PAIRS 64 #define CROSSASSET_MAX_CCY 16 #define CROSSASSET_FEATURES 6 //--- momentum horizons, in bars of the traded timeframe #define CROSSASSET_FAST_BARS 1 #define CROSSASSET_SLOW_BARS 20 //--- a reference pair must cover at least this fraction of the traded symbol's bars to be used #define CROSSASSET_MIN_COVERAGE 0.80 //+------------------------------------------------------------------+ //| Cross-asset panel, aligned to one symbol/timeframe bar grid. | //+------------------------------------------------------------------+ class CCrossAssetPanel { private: string m_symbol; ENUM_TIMEFRAMES m_period; int m_bars; // length of every array below bool m_ready; string m_baseCcy; string m_quoteCcy; //--- cumulative log strength per currency, [currency][bar], bar 0 = NEWEST (series order, //--- matching every other buffer in this codebase) string m_ccy[CROSSASSET_MAX_CCY]; int m_ccyCount; double m_strength[]; // flattened [ccyCount * m_bars] int m_baseIdx; int m_quoteIdx; //--- index mode only (m_baseIdx == m_quoteIdx): the risk-proxy currency's slot, -1 when none int m_riskIdx; //--- Train->serve parity: the pair set a model was TRAINED on, adopted from the .cfg. When set, //--- Build() uses exactly this list instead of re-discovering Market Watch, so adding/removing a //--- terminal symbol can no longer change what a trained model's features mean. A pinned pair //--- that is temporarily unavailable is skipped for that build (degraded, same as before), never //--- substituted. string m_pinnedPairs[]; //--- the pairs the LAST successful Build actually used - what a fresh model pins to its .cfg string m_usedPairsCsv; //--- cross-sectional dispersion of currency moves: a risk-on/risk-off proxy that belongs to no //--- single currency (wide dispersion = a directional FX day, narrow = drift/chop) double m_dispersion[]; bool DiscoverPairs(string &pairs[], string &pbase[], string &pquote[]); bool SeriesReady(string sym, ENUM_TIMEFRAMES tf, int need); int CurrencySlot(string ccy); int FindCurrency(string ccy) const; int SelectRiskProxy(void) const; double Strength(int ccyIdx, int bar) const { return(m_strength[ccyIdx * m_bars + bar]); } public: CCrossAssetPanel(void); ~CCrossAssetPanel(void); bool Build(string symbol, ENUM_TIMEFRAMES period, int bars); bool IsReady(void) const { return(m_ready); } int Bars(void) const { return(m_bars); } //--- CROSSASSET_FEATURES values for bar idx, all scale-free. Returns false only if the panel //--- was never built; a bar too close to the oldest edge yields a neutral 0-fill instead, the //--- same "degraded but usable" convention BufferTempDataCompute() uses for its swing block. bool Features(int idx, double &out[]); string BaseCurrency(void) const { return(m_baseCcy); } string QuoteCurrency(void) const { return(m_quoteCcy); } int CurrencyCount(void) const { return(m_ccyCount); } //--- pin/report the reference-pair set (comma-separated), see m_pinnedPairs void SetPinnedPairs(string csv); string UsedPairsCsv(void) const { return(m_usedPairsCsv); } bool HasPinnedPairs(void) const { return(ArraySize(m_pinnedPairs) > 0); } //--- Kick the terminal's ASYNCHRONOUS cross-symbol sync as early as possible (OnInit), so the //--- history download runs while the EA is still setting up instead of starting only when the //--- first Build() call trips over an unselected symbol. Non-blocking, exactly like SeriesReady. void Warm(ENUM_TIMEFRAMES period); }; //+------------------------------------------------------------------+ CCrossAssetPanel::CCrossAssetPanel(void) : m_symbol(""), m_period(PERIOD_CURRENT), m_bars(0), m_ready(false), m_baseCcy(""), m_quoteCcy(""), m_ccyCount(0), m_baseIdx(-1), m_quoteIdx(-1), m_riskIdx(-1) { } //+------------------------------------------------------------------+ CCrossAssetPanel::~CCrossAssetPanel(void) { } //+------------------------------------------------------------------+ //| Slot for a currency code, appending if new. -1 when full. | //+------------------------------------------------------------------+ int CCrossAssetPanel::CurrencySlot(string ccy) { for(int i = 0; i < m_ccyCount; i++) if(m_ccy[i] == ccy) return(i); if(m_ccyCount >= CROSSASSET_MAX_CCY) return(-1); m_ccy[m_ccyCount] = ccy; m_ccyCount++; return(m_ccyCount - 1); } //+------------------------------------------------------------------+ //| Adopt a pinned pair set ("EURUSD,USDJPY,..."), or clear with "". | //+------------------------------------------------------------------+ void CCrossAssetPanel::SetPinnedPairs(string csv) { ArrayResize(m_pinnedPairs, 0); string parts[]; int n = StringSplit(csv, ',', parts); for(int i = 0; i < n; i++) { StringTrimLeft(parts[i]); StringTrimRight(parts[i]); if(parts[i] == "") continue; int k = ArraySize(m_pinnedPairs); ArrayResize(m_pinnedPairs, k + 1); m_pinnedPairs[k] = parts[i]; } } //+------------------------------------------------------------------+ //| Start the async sync for every reference candidate NOW. Each | //| SymbolSelect/SeriesInfo touch queues a history build on the | //| terminal's own threads; by the time the first real Build() runs | //| the download has had its head start instead of starting there. | //+------------------------------------------------------------------+ void CCrossAssetPanel::Warm(ENUM_TIMEFRAMES period) { string pairs[], pbase[], pquote[]; if(HasPinnedPairs()) { //--- a trained model warms exactly the set it will use int n = ArraySize(m_pinnedPairs); ArrayResize(pairs, n); for(int i = 0; i < n; i++) pairs[i] = m_pinnedPairs[i]; } else if(!DiscoverPairs(pairs, pbase, pquote)) return; for(int i = 0; i < ArraySize(pairs); i++) SeriesReady(pairs[i], period, 1); // selects + touches; result deliberately ignored } //+------------------------------------------------------------------+ //| Lookup-only twin of CurrencySlot: never appends. -1 when absent. | //+------------------------------------------------------------------+ int CCrossAssetPanel::FindCurrency(string ccy) const { for(int i = 0; i < m_ccyCount; i++) if(m_ccy[i] == ccy) return(i); return(-1); } //+------------------------------------------------------------------+ //| Index mode's risk-proxy currency. A fixed preference order, not | //| "whatever slot came first": the pair set is discovered from | //| Market Watch at every build, so slot order is an accident of | //| discovery order - two builds with the same currencies must pick | //| the SAME proxy or the feature changes meaning between training | //| and serving. JPY leads because yen strength is the canonical FX | //| risk-off reading for equity indices; the rest rank by how much | //| of a safe-haven/beta signal each usually carries. | //+------------------------------------------------------------------+ int CCrossAssetPanel::SelectRiskProxy(void) const { string prefs[] = {"JPY", "CHF", "EUR", "GBP", "AUD", "NZD", "CAD"}; for(int i = 0; i < ArraySize(prefs); i++) { if(prefs[i] == m_baseCcy) continue; int slot = FindCurrency(prefs[i]); if(slot >= 0) return(slot); } //--- none of the preferred codes present: first registered currency that is not the denomination for(int i = 0; i < m_ccyCount; i++) if(m_ccy[i] != m_baseCcy) return(i); return(-1); } //+------------------------------------------------------------------+ //| Every Market Watch symbol that is an FX pair sharing a currency | //| with the traded symbol. Market Watch rather than the full broker | //| list on purpose: the full list can run to thousands of symbols, | //| most of them untraded CFDs whose history the terminal has never | //| downloaded, and forcing a sync on each would stall init. | //+------------------------------------------------------------------+ bool CCrossAssetPanel::DiscoverPairs(string &pairs[], string &pbase[], string &pquote[]) { ArrayResize(pairs, 0); ArrayResize(pbase, 0); ArrayResize(pquote, 0); int total = SymbolsTotal(true); for(int i = 0; i < total && ArraySize(pairs) < CROSSASSET_MAX_PAIRS; i++) { string s = SymbolName(i, true); if(s == "") continue; string b = SymbolInfoString(s, SYMBOL_CURRENCY_BASE); string q = SymbolInfoString(s, SYMBOL_CURRENCY_PROFIT); if(b == "" || q == "" || b == q) continue; //--- Only true FX crosses carry a clean "which currency moved" reading. A CFD on an index or //--- a metal reports a currency pair (e.g. XAUUSD -> XAU/USD, SP500 -> USD/USD-ish) but its //--- move is not a statement about the base CURRENCY, so folding it into a strength average //--- would inject the very single-series noise this panel exists to look past. if(StringLen(b) != 3 || StringLen(q) != 3) continue; if(SymbolInfoInteger(s, SYMBOL_TRADE_CALC_MODE) != SYMBOL_CALC_MODE_FOREX) continue; int n = ArraySize(pairs); ArrayResize(pairs, n + 1); ArrayResize(pbase, n + 1); ArrayResize(pquote, n + 1); pairs[n] = s; pbase[n] = b; pquote[n] = q; } return(ArraySize(pairs) > 0); } //+------------------------------------------------------------------+ //| Is sym/tf genuinely ready to be read to `need` bars? | //| | //| Cross-symbol access in MT5 is ASYNCHRONOUS: a Copy* call may | //| return partial data, or -1, simply because the terminal has not | //| finished building that series yet - with no error that | //| distinguishes it from "this symbol has no history at all". | //| Worse, SymbolIsSynchronized() (symbol-wide) and SERIES_SYNCHRONIZED| //| (this symbol AND this timeframe) can disagree, because the | //| terminal builds series on separate threads. Both are checked. | //| | //| Deliberately NON-BLOCKING - no retry loop, no Sleep. A pair that | //| is not ready is skipped for this build and picked up on a later | //| one; blocking here would stall OnTick for every unsynchronised | //| symbol in Market Watch. | //| | //| In the STRATEGY TESTER the agent loads auxiliary symbols from the | //| client terminal, NOT from the trade server - so a reference pair | //| whose history was never downloaded into the terminal is simply | //| absent, and no amount of waiting produces it. That is why a | //| failure here logs and degrades rather than retrying. | //+------------------------------------------------------------------+ bool CCrossAssetPanel::SeriesReady(string sym, ENUM_TIMEFRAMES tf, int need) { if(!(bool)SymbolInfoInteger(sym, SYMBOL_SELECT)) { //--- ask the terminal to start tracking it; it will not be ready THIS call SymbolSelect(sym, true); return(false); } if(!SymbolIsSynchronized(sym)) return(false); long synced = 0; if(!SeriesInfoInteger(sym, tf, SERIES_SYNCHRONIZED, synced) || synced == 0) return(false); long haveBars = 0; if(!SeriesInfoInteger(sym, tf, SERIES_BARS_COUNT, haveBars)) return(false); return(haveBars >= (long)(CROSSASSET_MIN_COVERAGE * need)); } //+------------------------------------------------------------------+ //| Build the panel for `bars` bars ending at the newest closed bar. | //+------------------------------------------------------------------+ bool CCrossAssetPanel::Build(string symbol, ENUM_TIMEFRAMES period, int bars) { m_ready = false; m_symbol = symbol; m_period = period; m_bars = bars; m_ccyCount = 0; m_baseIdx = m_quoteIdx = m_riskIdx = -1; if(bars <= CROSSASSET_SLOW_BARS + 2) return(false); m_baseCcy = SymbolInfoString(symbol, SYMBOL_CURRENCY_BASE); m_quoteCcy = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT); if(m_baseCcy == "" || m_quoteCcy == "") return(false); //--- the traded symbol's own bar grid: every reference series is resampled onto these timestamps datetime times[]; //--- Series flag BEFORE the copy, not after: this is what makes times[0] the NEWEST bar and so //--- match the index convention every caller here uses (BufferTempDataCompute's idx). Setting it //--- afterwards happens to work, but the whole panel is index arithmetic against this grid and a //--- silently reversed axis would not fail - it would just train on mirrored history. ArraySetAsSeries(times, true); if(CopyTime(symbol, period, 0, bars, times) != bars) { Print(__FUNCTION__ + ": could not read " + IntegerToString(bars) + " bar times for " + symbol + " - cross-asset features unavailable this run."); return(false); } string pairs[], pbase[], pquote[]; if(HasPinnedPairs()) { //--- A trained model builds from its PINNED set, never from discovery: Market Watch is mutable //--- terminal state, and a model whose features were trained against one pair set must not have //--- their meaning drift because a symbol was added or removed. Currencies are re-resolved from //--- the symbol (a symbol property, stable); a pinned pair the terminal no longer knows resolves //--- to empty and is skipped - degraded, logged below, never substituted. for(int i = 0; i < ArraySize(m_pinnedPairs); i++) { string b = SymbolInfoString(m_pinnedPairs[i], SYMBOL_CURRENCY_BASE); string q = SymbolInfoString(m_pinnedPairs[i], SYMBOL_CURRENCY_PROFIT); if(b == "" || q == "" || b == q) continue; int n = ArraySize(pairs); ArrayResize(pairs, n + 1); ArrayResize(pbase, n + 1); ArrayResize(pquote, n + 1); pairs[n] = m_pinnedPairs[i]; pbase[n] = b; pquote[n] = q; } if(ArraySize(pairs) == 0) { Print(__FUNCTION__ + ": none of the PINNED reference pairs resolve in this terminal - " "cross-asset features unavailable. The pin lives in the model's .cfg; this terminal " "does not know those symbols."); return(false); } } else if(!DiscoverPairs(pairs, pbase, pquote)) { Print(__FUNCTION__ + ": no FX pairs in Market Watch - cross-asset features unavailable."); return(false); } //--- accumulate per-currency log-return sums and the count of contributing pairs, per bar int maxCcy = CROSSASSET_MAX_CCY; double sum[]; int cnt[]; ArrayResize(sum, maxCcy * bars); ArrayResize(cnt, maxCcy * bars); ArrayInitialize(sum, 0.0); ArrayInitialize(cnt, 0); int used = 0; int skippedUnsynced = 0; string usedCsv = ""; for(int p = 0; p < ArraySize(pairs); p++) { if(!SeriesReady(pairs[p], period, bars)) { skippedUnsynced++; continue; } //--- Resample this pair onto the traded grid. CopyClose by TIME RANGE (not by index) is what //--- makes this safe across symbols that keep different session calendars: index 5 on GBPUSD //--- and index 5 on USDJPY are not the same instant if either had a gap. double pc[]; if(CopyClose(pairs[p], period, times[bars - 1], times[0], pc) <= 0) continue; datetime pt[]; if(CopyTime(pairs[p], period, times[bars - 1], times[0], pt) <= 0) continue; int pn = ArraySize(pc); if(pn < 2 || ArraySize(pt) != pn) continue; //--- pc/pt come back oldest-first from a range copy; walk the traded grid newest->oldest and //--- carry a cursor backwards through the reference series, taking the last reference bar at //--- or BEFORE each traded timestamp. Never a bar after it - that would be lookahead. double aligned[]; ArrayResize(aligned, bars); ArrayInitialize(aligned, 0.0); //--- Staleness cap. Matching "the last reference bar at or before this timestamp" is what makes //--- this robust to the tester's per-symbol tick sequences (bars genuinely do NOT open together //--- across symbols there, and on M1-M30 in "Open prices only" mode a non-chart symbol reads a //--- documented one bar stale). But an unbounded "at or before" would happily match a quote from //--- three days ago across a market holiday and call it this bar's price. Anything older than one //--- bar period is treated as no data rather than as a stale fill. int stale = (int)PeriodSeconds(period); int cur = pn - 1; int covered = 0; for(int b = 0; b < bars; b++) // b = 0 is the NEWEST traded bar { while(cur > 0 && pt[cur] > times[b]) cur--; if(pt[cur] <= times[b] && (int)(times[b] - pt[cur]) <= stale) { aligned[b] = pc[cur]; covered++; } } if(covered < (int)(CROSSASSET_MIN_COVERAGE * bars)) continue; // too gappy to be a trustworthy reference int bi = CurrencySlot(pbase[p]); int qi = CurrencySlot(pquote[p]); if(bi < 0 || qi < 0) continue; //--- one bar's log return, credited +ve to the base currency and -ve to the quote for(int b = 0; b < bars - 1; b++) { double a1 = aligned[b], a0 = aligned[b + 1]; if(a1 <= 0.0 || a0 <= 0.0) continue; double r = MathLog(a1 / a0); sum[bi * bars + b] += r; cnt[bi * bars + b] += 1; sum[qi * bars + b] -= r; cnt[qi * bars + b] += 1; } used++; usedCsv += (usedCsv == "" ? "" : ",") + pairs[p]; } if(used < 2 || m_ccyCount <= 0) { PrintFormat("%s: only %d usable reference pairs (%d skipped as unsynchronised/short) - " "cross-asset features unavailable this build, need >= 2. In the Strategy Tester the " "agent loads auxiliary symbols from the TERMINAL, not the server, so any reference " "pair whose history was never downloaded is permanently absent for that run.", __FUNCTION__, used, skippedUnsynced); return(false); } //--- per-bar average return per currency, then integrate newest<-oldest into a strength LEVEL so //--- multi-bar momentum is a plain difference of two levels ArrayResize(m_strength, m_ccyCount * bars); ArrayInitialize(m_strength, 0.0); ArrayResize(m_dispersion, bars); ArrayInitialize(m_dispersion, 0.0); for(int b = bars - 2; b >= 0; b--) { double mean = 0.0; int have = 0; double vals[CROSSASSET_MAX_CCY]; for(int cIdx = 0; cIdx < m_ccyCount; cIdx++) { int k = cIdx * bars + b; double r = (cnt[k] > 0) ? sum[k] / cnt[k] : 0.0; vals[cIdx] = r; m_strength[k] = m_strength[cIdx * bars + b + 1] + r; if(cnt[k] > 0) { mean += r; have++; } } //--- cross-sectional standard deviation of this bar's currency moves if(have > 1) { mean /= have; double v = 0.0; for(int cIdx = 0; cIdx < m_ccyCount; cIdx++) if(cnt[cIdx * bars + b] > 0) v += (vals[cIdx] - mean) * (vals[cIdx] - mean); m_dispersion[b] = MathSqrt(v / (have - 1)); } } m_baseIdx = CurrencySlot(m_baseCcy); m_quoteIdx = CurrencySlot(m_quoteCcy); if(m_baseIdx < 0 || m_quoteIdx < 0) { Print(__FUNCTION__ + ": traded symbol's currencies (" + m_baseCcy + "/" + m_quoteCcy + ") are not covered by any Market Watch pair - cross-asset features unavailable."); return(false); } m_usedPairsCsv = usedCsv; if(m_baseIdx == m_quoteIdx) { m_riskIdx = SelectRiskProxy(); m_ready = true; PrintFormat("%s: cross-asset panel built - %d reference pairs, %d currencies, %d bars " "(INDEX MODE: %s-denominated, risk proxy %s)", __FUNCTION__, used, m_ccyCount, bars, m_baseCcy, m_riskIdx >= 0 ? m_ccy[m_riskIdx] : "NONE"); return(true); } m_ready = true; PrintFormat("%s: cross-asset panel built - %d reference pairs, %d currencies, %d bars (%s/%s)", __FUNCTION__, used, m_ccyCount, bars, m_baseCcy, m_quoteCcy); return(true); } //+------------------------------------------------------------------+ //| Features for bar idx (series order: 0 = newest). | //+------------------------------------------------------------------+ bool CCrossAssetPanel::Features(int idx, double &out[]) { if(ArraySize(out) != CROSSASSET_FEATURES) ArrayResize(out, CROSSASSET_FEATURES); ArrayInitialize(out, 0.0); if(!m_ready) return(false); if(idx < 0 || idx + CROSSASSET_SLOW_BARS + 1 >= m_bars) return(true); // degraded-but-usable neutral fill near the edges //--- Strength moves are log returns summed across pairs, so they already live in a small, //--- symbol-independent range. Scale by 100 to put a typical H1 move near unity rather than //--- near 0.001, which would vanish into rounding against the ATR-normalised price features, //--- then clamp: the clamp is what stops a flash-crash bar from dominating a whole batch. double c1 = iClose(m_symbol, m_period, idx); double c0 = iClose(m_symbol, m_period, idx + CROSSASSET_SLOW_BARS); if(m_baseIdx != m_quoteIdx) { //--- FX MODE: the traded symbol IS a currency pair, so its two sides each have an index. double bFast = (Strength(m_baseIdx, idx) - Strength(m_baseIdx, idx + CROSSASSET_FAST_BARS)) * 100.0; double qFast = (Strength(m_quoteIdx, idx) - Strength(m_quoteIdx, idx + CROSSASSET_FAST_BARS)) * 100.0; double bSlow = (Strength(m_baseIdx, idx) - Strength(m_baseIdx, idx + CROSSASSET_SLOW_BARS)) * 100.0; double qSlow = (Strength(m_quoteIdx, idx) - Strength(m_quoteIdx, idx + CROSSASSET_SLOW_BARS)) * 100.0; out[0] = MathMax(-5.0, MathMin(5.0, bFast)); out[1] = MathMax(-5.0, MathMin(5.0, qFast)); out[2] = MathMax(-5.0, MathMin(5.0, bSlow)); out[3] = MathMax(-5.0, MathMin(5.0, qSlow)); //--- DIVERGENCE: what the pair itself did over the slow window, minus what its two currencies //--- separately say it should have done. This is the one feature here that cannot be derived //--- from the traded series at all - it is only defined relative to the rest of the market, and //--- it is the panel's actual thesis: a pair that has NOT kept up with its own currencies is in //--- a different state from one that led them. if(c1 > 0.0 && c0 > 0.0) { double own = MathLog(c1 / c0) * 100.0; out[4] = MathMax(-5.0, MathMin(5.0, own - (bSlow - qSlow))); } } else { //--- INDEX MODE (base == quote, e.g. SP500 -> USD/USD): the FX slots would be the same series //--- twice and a divergence of exactly zero information. Re-encoded (see the file header): //--- 0/2 = DENOMINATION currency strength (fast/slow). A USD-priced index is mechanically //--- pressured when USD strengthens - the denominator effect. //--- 1/3 = RISK-PROXY currency strength (fast/slow), JPY by preference: yen strength is the //--- canonical FX risk-off tell for equities. 0-fill when no proxy currency exists. //--- 4 = own slow move minus what the denomination alone implies (-dSlow), i.e. own + dSlow: //--- an index holding its level THROUGH a strengthening denomination is genuinely bid. double dFast = (Strength(m_baseIdx, idx) - Strength(m_baseIdx, idx + CROSSASSET_FAST_BARS)) * 100.0; double dSlow = (Strength(m_baseIdx, idx) - Strength(m_baseIdx, idx + CROSSASSET_SLOW_BARS)) * 100.0; double rFast = 0.0, rSlow = 0.0; if(m_riskIdx >= 0) { rFast = (Strength(m_riskIdx, idx) - Strength(m_riskIdx, idx + CROSSASSET_FAST_BARS)) * 100.0; rSlow = (Strength(m_riskIdx, idx) - Strength(m_riskIdx, idx + CROSSASSET_SLOW_BARS)) * 100.0; } out[0] = MathMax(-5.0, MathMin(5.0, dFast)); out[1] = MathMax(-5.0, MathMin(5.0, rFast)); out[2] = MathMax(-5.0, MathMin(5.0, dSlow)); out[3] = MathMax(-5.0, MathMin(5.0, rSlow)); if(c1 > 0.0 && c0 > 0.0) { double own = MathLog(c1 / c0) * 100.0; out[4] = MathMax(-5.0, MathMin(5.0, own + dSlow)); } } //--- cross-sectional dispersion, averaged over the slow window: regime context that belongs to //--- the market as a whole rather than to either currency double disp = 0.0; for(int k = 0; k < CROSSASSET_SLOW_BARS; k++) disp += m_dispersion[idx + k]; out[5] = MathMax(0.0, MathMin(5.0, (disp / CROSSASSET_SLOW_BARS) * 1000.0)); return(true); } #endif // WARRIOR_CROSSASSET_MQH