//+------------------------------------------------------------------+ //| TradingView 1:1 Execution EA for MetaTrader 5 | //| Executes the exact signals produced by the TradingView strategy | //| File format: ACTION;SYMBOL;ENTRY;SL;TP1;TP2;TPFINAL;ID | //| Example: BUY;EURUSD;1.08500;1.08300;1.08700;1.08900;1.10100;TV01 | //+------------------------------------------------------------------+ #property strict #property version "1.00" #include CTrade trade; //============================== INPUTS ============================== input string InpSignalFileName = "tv_signal.txt"; // Common/Files/tv_signal.txt input bool InpUseCommonFolder = true; input int InpMagic = 26050601; input double InpLots = 0.10; input bool InpUseRiskPercent = false; input double InpRiskPercent = 1.0; input bool InpUsePartials = true; input double InpTP1Percent = 30.0; input double InpTP2Percent = 30.0; input double InpFinalPercent = 40.0; input int InpSlippagePoints = 20; input bool InpOnePositionAtATime = true; input bool InpCloseOpposite = true; input int InpPollSeconds = 1; //============================== GLOBALS ============================== string g_last_id = ""; datetime g_last_poll = 0; //============================== HELPERS ============================== string Trim(string s) { StringTrimLeft(s); StringTrimRight(s); return s; } int Split(string text, string sep, string &arr[]) { return StringSplit(text, StringGetCharacter(sep, 0), arr); } double NormalizeLots(double lots) { double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); lots = MathMax(minLot, MathMin(maxLot, lots)); lots = MathFloor(lots / step) * step; int digits = 2; if(step == 0.001) digits = 3; if(step == 0.01) digits = 2; if(step == 0.1) digits = 1; return NormalizeDouble(lots, digits); } double LotsByRisk(double entry, double sl) { if(!InpUseRiskPercent) return NormalizeLots(InpLots); double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskMoney = balance * InpRiskPercent / 100.0; double dist = MathAbs(entry - sl); if(dist <= 0.0) return NormalizeLots(InpLots); double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(tickValue <= 0.0 || tickSize <= 0.0) return NormalizeLots(InpLots); double lossPerLot = dist / tickSize * tickValue; if(lossPerLot <= 0.0) return NormalizeLots(InpLots); return NormalizeLots(riskMoney / lossPerLot); } bool IsOurPosition(ulong ticket) { if(!PositionSelectByTicket(ticket)) return false; if(PositionGetString(POSITION_SYMBOL) != _Symbol) return false; if((int)PositionGetInteger(POSITION_MAGIC) != InpMagic) return false; return true; } bool HasOurPosition() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(IsOurPosition(ticket)) return true; } return false; } void CloseOppositePositions(bool buySignal) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!IsOurPosition(ticket)) continue; long type = PositionGetInteger(POSITION_TYPE); if(buySignal && type == POSITION_TYPE_SELL) trade.PositionClose(ticket); if(!buySignal && type == POSITION_TYPE_BUY) trade.PositionClose(ticket); } } bool ReadSignalLine(string &line) { int flags = FILE_READ | FILE_TXT | FILE_ANSI; if(InpUseCommonFolder) flags |= FILE_COMMON; int handle = FileOpen(InpSignalFileName, flags); if(handle == INVALID_HANDLE) return false; line = ""; while(!FileIsEnding(handle)) { string tmp = FileReadString(handle); if(StringLen(Trim(tmp)) > 0) line = Trim(tmp); } FileClose(handle); return StringLen(line) > 0; } bool ExecuteSingleOrder(bool isBuy, double lots, double sl, double tp, string comment) { trade.SetExpertMagicNumber(InpMagic); trade.SetDeviationInPoints(InpSlippagePoints); bool ok = false; if(isBuy) ok = trade.Buy(lots, _Symbol, 0.0, sl, tp, comment); else ok = trade.Sell(lots, _Symbol, 0.0, sl, tp, comment); if(!ok) { Print("Order failed: ", comment, " retcode=", trade.ResultRetcode(), " desc=", trade.ResultRetcodeDescription()); } return ok; } bool ExecuteSignal(string line) { string p[]; int n = Split(line, ";", p); if(n < 8) { Print("Invalid signal format. Expected 8 fields: ", line); return false; } string action = StringUpper(Trim(p[0])); string symbol = Trim(p[1]); if(symbol != _Symbol) { Print("Signal ignored. Signal symbol=", symbol, " chart symbol=", _Symbol); return false; } double entry = StringToDouble(Trim(p[2])); double sl = NormalizeDouble(StringToDouble(Trim(p[3])), _Digits); double tp1 = NormalizeDouble(StringToDouble(Trim(p[4])), _Digits); double tp2 = NormalizeDouble(StringToDouble(Trim(p[5])), _Digits); double tpFinal = NormalizeDouble(StringToDouble(Trim(p[6])), _Digits); string id = Trim(p[7]); if(id == "" || id == g_last_id) return false; bool isBuy = action == "BUY" || action == "LONG" || action == "BULL"; bool isSell = action == "SELL" || action == "SHORT" || action == "BEAR"; if(!isBuy && !isSell) { Print("Invalid action: ", action); return false; } if(InpCloseOpposite) CloseOppositePositions(isBuy); if(InpOnePositionAtATime && HasOurPosition()) { Print("Signal ignored: existing position and InpOnePositionAtATime=true"); return false; } double lotsTotal = LotsByRisk(entry, sl); if(lotsTotal <= 0.0) { Print("Invalid lots"); return false; } bool ok = true; if(!InpUsePartials) { ok = ExecuteSingleOrder(isBuy, lotsTotal, sl, tpFinal, "TV 1:1 " + id); } else { double lot1 = NormalizeLots(lotsTotal * InpTP1Percent / 100.0); double lot2 = NormalizeLots(lotsTotal * InpTP2Percent / 100.0); double lot3 = NormalizeLots(lotsTotal * InpFinalPercent / 100.0); double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); if(lot1 < minLot) lot1 = 0.0; if(lot2 < minLot) lot2 = 0.0; if(lot3 < minLot) lot3 = 0.0; if(lot1 + lot2 + lot3 <= 0.0) lot3 = lotsTotal; if(lot1 > 0.0) ok = ExecuteSingleOrder(isBuy, lot1, sl, tp1, "TV 1:1 TP1 " + id) && ok; if(lot2 > 0.0) ok = ExecuteSingleOrder(isBuy, lot2, sl, tp2, "TV 1:1 TP2 " + id) && ok; if(lot3 > 0.0) ok = ExecuteSingleOrder(isBuy, lot3, sl, tpFinal, "TV 1:1 FINAL " + id) && ok; } if(ok) { g_last_id = id; Print("Executed TradingView signal: ", line); } return ok; } //============================== MT5 EVENTS ============================ int OnInit() { trade.SetExpertMagicNumber(InpMagic); EventSetTimer(InpPollSeconds); Print("TradingView 1:1 Execution EA started."); Print("Signal file: Common/Files/", InpSignalFileName); Print("Format: ACTION;SYMBOL;ENTRY;SL;TP1;TP2;TPFINAL;ID"); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { EventKillTimer(); Print("TradingView 1:1 Execution EA stopped."); } void OnTick() { // Trading is handled in OnTimer so it can react even without a new tick. } void OnTimer() { string line; if(ReadSignalLine(line)) ExecuteSignal(line); } //+------------------------------------------------------------------+ //==================================================================== // TradingView Alert-Format für deine Pine Strategy //==================================================================== // // Damit der MT5-EA 1:1 dieselben Signale handelt, muss TradingView // bei jedem echten Strategy-Signal eine Zeile in folgendem Format // an deinen Bridge-Dienst senden: // // BUY;{{ticker}};ENTRY;SL;TP1;TP2;TPFINAL;UNIQUE_ID // SELL;{{ticker}};ENTRY;SL;TP1;TP2;TPFINAL;UNIQUE_ID // // In deinem Pine Script sind die Werte bereits vorhanden: // // Bear confirmed: // bear_entry, bear_sl, short_tp1, short_tp2, bear_tp // // Bull confirmed: // bull_entry, bull_sl, long_tp1, long_tp2, bull_tp // // Final bear: // bear_entry, bear_sl, short_tp1, short_tp2, bear_tp // // Final bull: // bull_entry, bull_sl, long_tp1, long_tp2, bull_tp // // Beispiel Alert-Message in Pine: // // alert("SELL;" + syminfo.ticker + ";" + // str.tostring(bear_entry, format.mintick) + ";" + // str.tostring(bear_sl, format.mintick) + ";" + // str.tostring(short_tp1, format.mintick) + ";" + // str.tostring(short_tp2, format.mintick) + ";" + // str.tostring(bear_tp, format.mintick) + ";" + // "FINAL_BEAR_" + str.tostring(time), // alert.freq_once_per_bar) // // Der Bridge-Dienst schreibt diese Message als letzte Zeile in: // MetaTrader 5 / Common / Files / tv_signal.txt // // Dadurch bestimmt TradingView exakt Entry, SL und TP. // MT5 führt nur aus. //====================================================================