//|------------------------------------------------------------------+ //| Unity.mq5 | //| Copyright (c) 2018, Marketeer | //| https://www.mql5.com/en/users/marketeer | //| https://www.mql5.com/en/articles/5473/ | //| https://www.mql5.com/en/code/26112 | //|------------------------------------------------------------------+ #property copyright "2018 © Marketeer" #property link "https://www.mql5.com/en/users/marketeer" #property version "1.0" #property description "Multi-asset cluster indicator taking all currencies as a sum of squares forming market unity (1.0)" #define BUF_NUM 15 #property indicator_separate_window #property indicator_buffers BUF_NUM #property indicator_plots BUF_NUM #property indicator_color1 Green #property indicator_color2 DarkBlue #property indicator_color3 Red #property indicator_color4 Gray #property indicator_color5 Peru #property indicator_color6 Gold #property indicator_color7 Purple #property indicator_color8 Teal #property indicator_color9 LightGreen #property indicator_color10 LightBlue #property indicator_color11 Orange #property indicator_color12 LightGray #property indicator_color13 Brown #property indicator_color14 Yellow #property indicator_color15 Magenta input string _1 = ""; // Cluster Indicator Settings input string Instruments = "EURUSD,GBPUSD,USDCHF,USDJPY,AUDUSD,USDCAD,NZDUSD"; // · Instruments input int BarLimit = 500; // · BarLimit input ENUM_DRAW_TYPE Draw = DRAW_LINE; // · Draw input ENUM_APPLIED_PRICE PriceType = PRICE_CLOSE; // · PriceType input bool AbsoluteValues = false; // · AbsoluteValues input string _2 = ""; // Data Export to CSV input string SaveToFile = ""; // · SaveToFile input bool ShiftLastBuffer = false; // · ShiftLastBuffer input int BarLookback = 1; // · BarLookback int ShiftedBuffer = -1; bool initDone = false; #include #include IndicatorArray buffers(BUF_NUM); IndicatorArrayGetter getter(buffers); string Symbols[]; int Direction[]; double Contracts[]; int SymbolCount; HashMapTemplate workCurrencies; int baseIndex = 0; string BaseCurrency; int LastBarCount; bool initSymbols() { SymbolCount = StringSplit(Instruments, ',', Symbols); if(SymbolCount >= BUF_NUM) { SymbolCount = BUF_NUM - 1; ArrayResize(Symbols, SymbolCount); } else if(SymbolCount == 0) { SymbolCount = 1; ArrayResize(Symbols, SymbolCount); Symbols[0] = Symbol(); } ArrayResize(Direction, SymbolCount); ArrayInitialize(Direction, 0); ArrayResize(Contracts, SymbolCount); ArrayInitialize(Contracts, 1); string base = NULL; for(int i = 0; i < SymbolCount; i++) { if(!SymbolSelect(Symbols[i], true)) { Print("Can't select ", Symbols[i]); return false; } string first, second; first = SymbolInfoString(Symbols[i], SYMBOL_CURRENCY_BASE); second = SymbolInfoString(Symbols[i], SYMBOL_CURRENCY_PROFIT); if(first != second) { workCurrencies.inc(first, 1); workCurrencies.inc(second, 1); } else { workCurrencies.inc(Symbols[i], 1); } } if(workCurrencies.getSize() >= BUF_NUM) { Print("Too many symbols, max ", (BUF_NUM - 1)); return false; } if(ShiftLastBuffer) { ShiftedBuffer = workCurrencies.getSize() - 1; } for(int i = 0; i < workCurrencies.getSize(); i++) { if(workCurrencies[i] > 1) { if(base == NULL) { base = workCurrencies.getKey(i); baseIndex = i; } else { Print("Collision: multiple base symbols: ", base, "[", workCurrencies[base], "] ", workCurrencies.getKey(i), "[", workCurrencies[i], "]"); return false; } } } BaseCurrency = base; for(int i = 0; i < SymbolCount; i++) { if(SymbolInfoString(Symbols[i], SYMBOL_CURRENCY_PROFIT) == base) Direction[i] = +1; else if(SymbolInfoString(Symbols[i], SYMBOL_CURRENCY_BASE) == base) Direction[i] = -1; else { Print("Ambiguous symbol direction ", Symbols[i], ", defaults used"); Direction[i] = +1; } Contracts[i] = SymbolInfoDouble(Symbols[i], SYMBOL_TRADE_CONTRACT_SIZE); } return true; } int OnInit() { ShiftedBuffer = -1; initDone = false; if(!initSymbols()) return INIT_PARAMETERS_INCORRECT; string base = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE); string profit = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT); int replaceIndex = -1; for(int i = 0; i <= SymbolCount; i++) { string name; if(i == 0) { name = BaseCurrency; if(name != workCurrencies.getKey(i)) { replaceIndex = i; } } else { if(BaseCurrency == workCurrencies.getKey(i) && replaceIndex > -1) { name = workCurrencies.getKey(replaceIndex); } else { name = workCurrencies.getKey(i); } } PlotIndexSetString(i, PLOT_LABEL, name); int width = (name == base || name == profit) ? 2 : 1; PlotIndexSetInteger(i, PLOT_DRAW_TYPE, Draw); PlotIndexSetInteger(i, PLOT_LINE_WIDTH, width); PlotIndexSetInteger(i, PLOT_SHOW_DATA, true); } for(int i = SymbolCount + 1; i < BUF_NUM; i++) { PlotIndexSetInteger(i, PLOT_SHOW_DATA, false); } if(!AbsoluteValues) { IndicatorSetInteger(INDICATOR_LEVELS, 1); IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 0.0); } IndicatorSetString(INDICATOR_SHORTNAME, "Unity [" + (string)workCurrencies.getSize() + "] " + EnumToString(PriceType)); IndicatorSetInteger(INDICATOR_DIGITS, 5); LastBarCount = 0; initDone = true; EventSetTimer(1); Print("Unity ", __FUNCTION__, " ", Bars(_Symbol, PERIOD_CURRENT)); return INIT_SUCCEEDED; } #define CopyXYZ(N, S, B, A) if(Copy##N(S, PERIOD_CURRENT, B, 1, A) != 1) {Print("Failed Copy", #N, " ", S, " ", B, " ", GetLastError()); return false;} #define CopyXYZS(N, B, A) if(Copy##N(_Symbol, PERIOD_CURRENT, B, 1, A) != 1) {Print("Failed Copy", #N, " ", _Symbol, " ", B, " ", GetLastError()); return false;} #define CopyXYZN(N, B, C, A) if(Copy##N(_Symbol, PERIOD_CURRENT, B, C, A) != C) {Print("Failed Copy", #N, " ", _Symbol, " ", B, " ", GetLastError()); return false;} #define CopyXYZSN(N, S, B, C, A) if(Copy##N(S, PERIOD_CURRENT, B, C, A) != C) {Print("Failed Copy", #N, " ", S, " ", B, " ", GetLastError()); return false;} #define TOSTRING(A) #A + " = " + (string)(A) + " " bool calculate(const int bar) { datetime time[1]; CopyXYZN(Time, bar, 1, time); double w[2] = {0}; double v[][2]; ArrayResize(v, SymbolCount); for(int j = 0; j < SymbolCount; j++) { if(PriceType == PRICE_OPEN) { CopyXYZSN(Open, Symbols[j], time[0], 2, w); } else if(PriceType == PRICE_CLOSE) { CopyXYZSN(Close, Symbols[j], time[0], 2, w); } if(Direction[j] == -1) { w[0] = 1.0 / w[0]; w[1] = 1.0 / w[1]; } w[1] *= Contracts[j]; w[0] *= Contracts[j]; v[j][0] = w[1]; v[j][1] = w[0]; } double sum[2] = {1.0, 1.0}; for(int j = 0; j < SymbolCount; j++) { sum[0] += v[j][0] * v[j][0]; sum[1] += v[j][1] * v[j][1]; } double base2_0 = 1.0 / sum[0]; double base2_1 = 1.0 / sum[1]; double base_0 = MathSqrt(base2_0); double base_1 = MathSqrt(base2_1); if(AbsoluteValues) { buffers[0][bar] = base_0 * Contracts[0]; } else { buffers[0][bar] = base_0 / base_1 - 1.0; } for(int j = 1; j <= SymbolCount; j++) { if(AbsoluteValues) { buffers[j][bar] = base_0 * v[j - 1][0]; } else { buffers[j][bar] = base_0 * v[j - 1][0] / (base_1 * v[j - 1][1]) - 1.0; } } return true; } void OnTimer() { const double price[] = {}; if(LastBarCount != Bars(_Symbol, PERIOD_CURRENT)) { int n = OnCalculate(Bars(_Symbol, PERIOD_CURRENT), 0, 0, price); if(n > 0) { EventKillTimer(); ChartSetSymbolPeriod(0, _Symbol, PERIOD_CURRENT); ChartRedraw(); } } else { EventKillTimer(); } } int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[]) { if(!initDone) return 0; if(LastBarCount == rates_total && prev_calculated == rates_total && PriceType != PRICE_CLOSE) { return prev_calculated; } if(prev_calculated == 0) { for(int i = 0; i < rates_total; i++) { for(int j = 0; j < BUF_NUM; j++) { buffers[j][i] = EMPTY_VALUE; } } } int limit = MathMin(rates_total - prev_calculated + 1, BarLimit); for(int i = 0; i < limit; i++) { if(!calculate(i)) { return 0; // will retry on next tick } } static bool fileSaved = SaveToFile == ""; if(!fileSaved && !AbsoluteValues) { SaveBuffersToFile(SaveToFile); fileSaved = true; } LastBarCount = rates_total; return LastBarCount; } double GetBuffer(int index, int bar) { return getter[index][bar]; } bool SaveBuffersToFile(const string filename) { int h = FileOpen(filename, FILE_WRITE | FILE_CSV | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_ANSI, ';'); if(h == INVALID_HANDLE) return false; string line = "datetime"; for(int k = BarLookback - 1; k >= 0; k--) { for(int i = 0; i < workCurrencies.getSize(); i++) { line += ";" + workCurrencies.getKey(i) + (string)(k + 1); } } if(ShiftLastBuffer) { line += ";FORECAST"; } FileWriteString(h, line + "\n"); for(int i = BarLimit - BarLookback; i >= (ShiftedBuffer > -1 ? 1 : 0); i--) { datetime time[1]; CopyXYZN(Time, i, 1, time); line = (string)time[0]; for(int k = BarLookback - 1; k >= 0; k--) { for(int j = 0; j < workCurrencies.getSize(); j++) { line += ";" + (string)GetBuffer(j, i + k); } } if(ShiftLastBuffer) { line += ";" + (string)GetBuffer(ShiftedBuffer, i - 1); // look into the future for 1 bar } FileWriteString(h, line + "\n"); } FileClose(h); Print("File saved ", filename); return true; }