Why do we want Google Apps Script?
To trade commerce knowledge between separate MetaTrader terminals, a easy relay server is required. Google Apps Script acts as a free middleman that transfers commerce occasions from the Grasp account to the Slave account. It ensures dependable supply of occasions even after web interruptions or terminal restarts and doesn’t require a VPS or devoted server.
Find out how to create and deploy Google Apps Script
Go to https://script.google.com and Click on Begin scripting
Click on New challenge

Delete the default code and paste the script offered under the instruction steps

Press Ctrl + S to save lots of the challenge (the highest menu will grow to be lively)

Click on Deploy within the top-right nook and choose New deployment

Within the opened window, click on Choose sort (⚙️) and select Net app

In the Description subject, enter Model 1 (any textual content is okay). Set Who has entry to Anybody and depart Execute as unchanged

Click on Deploy – your Apps Script URL shall be generated. Copy and paste this URL into the EA enter settings

const API_KEYÂ Â = ‘I_AM_API_KEY’;
const MAX_PRUNE = 200;
const CONSUMER_TTL_MS = 6 * 60 * 60 * 1000;
const MAX_CONSUMERS_PER_CHANNEL = 50;
operate doPost(e) {
  const lock = LockService.getScriptLock();
  let locked = false;
  attempt {
    lock.waitLock(10000);
    locked = true;
    if (!e || !e.postData || !e.parameter) return _resp({ okay:false, error:‘no knowledge’ });
    const key = (e.parameter.key || ”).toString();
    if (key !== API_KEY) return _resp({ okay:false, error:‘forbidden’ });
    const channel  = (e.parameter.channel || ‘default’).toString();
    const client = (e.parameter.client || ”).toString();
    const c        = client || ‘single’;
    const retailer = PropertiesService.getScriptProperties();
   Â
    let uncooked = e.postData.contents || ‘{}’;
    uncooked = uncooked.substitute(/[u0000-u001F]+$/g, ”);
    let physique;
    attempt {
      physique = JSON.parse(uncooked);
    } catch (parseErr) {
      return _resp({ okay:false, error:‘dangerous json’, particulars:String(parseErr) });
    }
   Â
   Â
    _touchConsumerFast(retailer, channel, c);
   Â
    if (physique && physique.motion === ‘ack’) {
      const lastId = Quantity(physique.last_id || 0);
      if (!lastId) return _resp({ okay:false, error:‘dangerous ack’ });
      retailer.setProperty(_ackKey(channel, c), String(lastId));
     Â
      _pruneByMinAckFast(retailer, channel);
      return _resp({ okay:true, ack:lastId });
    }
   Â
    const nextId = _nextSeq(retailer, channel);
    physique.id = nextId;
    physique.server_time_ms = Date.now();
   Â
    retailer.setProperty(_evKey(channel, nextId), JSON.stringify(physique));
   Â
    const minKey = _minKey(channel);
    const curMin = Quantity(retailer.getProperty(minKey) || ‘0’);
    if (!curMin) retailer.setProperty(minKey, String(nextId));
    return _resp({ okay:true, last_id: nextId });
  } catch (err) {
    return _resp({
      okay:false,
      error:‘exception’,
      message:String(err),
      stack:(err && err.stack) ? String(err.stack) : ”
    });
  } lastly {
    if (locked) {
      attempt { lock.releaseLock(); } catch(_) {}
    }
  }
}
operate doGet(e) {
  const lock = LockService.getScriptLock();
  let locked = false;
  attempt {
    lock.waitLock(10000);
    locked = true;
    if (!e || !e.parameter) return _resp({ okay:false, error:‘no params’ });
    const key = (e.parameter.key || ”).toString();
    if (key !== API_KEY) return _resp({ okay:false, error:‘forbidden’ });
    const channel  = (e.parameter.channel || ‘default’).toString();
    const client = (e.parameter.client || ”).toString();
    const c        = client || ‘single’;
    const restrict    = Math.max(1, Math.min(100, Quantity(e.parameter.restrict || 20)));
    const retailer = PropertiesService.getScriptProperties();
   Â
   Â
    _touchConsumerFast(retailer, channel, c);
    const minId = Quantity(retailer.getProperty(_minKey(channel)) || ‘0’);
    const seq  = Quantity(retailer.getProperty(_seqKey(channel)) || ‘0’);
    const ackKey = _ackKey(channel, c);
    let ack = Quantity(retailer.getProperty(ackKey) || ‘0’);
   Â
    if (minId > 0) {
      const floorAck = Math.max(0, minId – 1);
      if (ack < floorAck) {
        ack = floorAck;
        retailer.setProperty(ackKey, String(ack));
      }
    }
   Â
    const mode = (e.parameter.mode || ”).toString();
    if (mode === ‘well being’ || mode === ‘debug’) {
      const shoppers = _listActiveConsumersFast(retailer, channel);
      const minAck = _minAckFast(retailer, channel, shoppers);
      const out = { okay:true, channel, client:c, ack, seq, min_id:minId, active_consumers:shoppers, min_ack:minAck };
      if (mode === ‘debug’) {
       Â
        const seen = {};
        for (const cc of shoppers) ‘0’);
       Â
        out.seen = seen;
      }
      return _resp(out);
    }
    const occasions = [];
    let missing_id = 0;
   Â
    for (let id = ack + 1; id <= seq && occasions.size < restrict; id++) {
      const evStr = retailer.getProperty(_evKey(channel, id));
      if (!evStr) {
        missing_id = id;
        break;
      }
      attempt {
        occasions.push(JSON.parse(evStr));
      } catch (parseErr) {
        missing_id = id;
        break;
      }
    }
   Â
    if (missing_id && minId > 0 && missing_id < minId) {
      const newAck = Math.max(0, minId – 1);
      retailer.setProperty(ackKey, String(newAck));
      const events2 = [];
      let missing2 = 0;
      for (let id = newAck + 1; id <= seq && events2.size < restrict; id++) {
        const evStr = retailer.getProperty(_evKey(channel, id));
        if (!evStr) { missing2 = id; break; }
        attempt { events2.push(JSON.parse(evStr)); } catch (_) { missing2 = id; break; }
      }
      if (!missing2) {
        return _resp({ okay:true, ack: newAck, seq: seq, occasions: events2 });
      }
     Â
      return _resp({
        okay:false,
        error:‘gap_detected’,
        ack: newAck,
        seq: seq,
        missing_id: missing2
      });
    }
    if (missing_id) {
      return _resp({
        okay:false,
        error:‘gap_detected’,
        ack: ack,
        seq: seq,
        missing_id: missing_id
      });
    }
    return _resp({ okay:true, ack: ack, seq: seq, occasions: occasions });
  } catch (err) {
    return _resp({
      okay:false,
      error:‘exception’,
      message:String(err),
      stack:(err && err.stack) ? String(err.stack) : ”
    });
  } lastly {
    if (locked) {
      attempt { lock.releaseLock(); } catch(_) {}
    }
  }
}
operate _nextSeq(retailer, channel) ‘0’) + 1;
  retailer.setProperty(ok, String(subsequent));
  return subsequent;
operate _touchConsumerFast(retailer, channel, client) {
  const now = Date.now();
  retailer.setProperty(_seenKey(channel, client), String(now));
  const listKey = _consumersKey(channel);
  let arr = [];
  attempt catch(_) { arr = []; }
  if (arr.indexOf(client) < 0) {
    arr.push(client);
   Â
    if (arr.size > MAX_CONSUMERS_PER_CHANNEL) arr = arr.slice(arr.size – MAX_CONSUMERS_PER_CHANNEL);
    retailer.setProperty(listKey, JSON.stringify(arr));
  }
  const ackKey = _ackKey(channel, client);
  const ackStr = retailer.getProperty(ackKey);
 Â
 Â
  if (ackStr === null || ackStr === undefined || ackStr === ”)
 Â
  const minId = Quantity(retailer.getProperty(_minKey(channel)) || ‘0’);
  const ack = Quantity(ackStr || ‘0’);
  if (minId > 0) {
    const floorAck = Math.max(0, minId – 1);
    if (ack < floorAck) retailer.setProperty(ackKey, String(floorAck));
  }
}
operate _listActiveConsumersFast(retailer, channel) {
  const now = Date.now();
  const listKey = _consumersKey(channel);
  let arr = [];
  attempt catch(_) { arr = []; }
  const lively = [];
  for (const c of arr)
  if (lively.size === 0) lively.push(‘single’);
  return lively;
}
operate _minAckFast(retailer, channel, shoppers) {
  let min = null;
  for (const c of shoppers)
  return min === null ? 0 : min;
}
operate _pruneByMinAckFast(retailer, channel) {
  const shoppers = _listActiveConsumersFast(retailer, channel);
  const minAck = _minAckFast(retailer, channel, shoppers);
  if (minAck <= 0) return;
  _pruneAckedUpTo(retailer, channel, minAck);
}
operate _pruneAckedUpTo(retailer, channel, ackId) {
  const minKey = _minKey(channel);
  let minId = Quantity(retailer.getProperty(minKey) || ‘0’);
  if (!minId) return;
  let eliminated = 0;
  whereas (minId && minId <= ackId && eliminated < MAX_PRUNE) {
    retailer.deleteProperty(_evKey(channel, minId));
    minId++;
    eliminated++;
  }
  const seq = Quantity(retailer.getProperty(_seqKey(channel)) || ‘0’);
  if (minId > seq) {
    retailer.deleteProperty(minKey);
  } else {
    retailer.setProperty(minKey, String(minId));
  }
}
operate _seqKey(channel) { return channel + ‘__seq’; }
operate _minKey(channel) { return channel + ‘__min’; }
operate _ackKey(channel, client) { return channel + ‘__ack__’ + client; }
operate _evKey(channel, id) { return channel + ‘__ev__’ + id; }
operate _seenKey(channel, client) { return channel + ‘__seen__’ + client; }
operate _consumersKey(channel) { return channel + ‘__consumers’; }
operate _resp(obj) {
 Â
 Â
 Â
  return ContentService
    .createTextOutput(JSON.stringify(obj))
    .setMimeType(ContentService.MimeType.JSON);
}








