Methods to setup Grid Copier with Google Apps Script (step-by-step) – Different – 18 February 2026


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.

Methods to create and deploy Google Apps Script

  1. Go to https://script.google.com and Click on Begin scripting

  2. Click on New undertaking


  3. Delete the default code and paste the script supplied under the instruction steps


  4. Press Ctrl + S to avoid wasting the undertaking (the highest menu will turn out to be lively)


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

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


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


  8. Click on Deploy – your Apps Script URL will likely 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;



perform doPost(e) {
  const lock = LockService.getScriptLock();
  let locked = false;

  strive  'single';

    const retailer = PropertiesService.getScriptProperties();

    
    let uncooked = e.postData.contents  catch (err) 
          seen(cc) = Quantity(retailer.getProperty(_seenKey(channel, cc))  lastly 
}

perform doGet(e) {
  const lock = LockService.getScriptLock();
  let locked = false;

  strive {
    lock.waitLock(10000);
    locked = true;

    if (!e || !e.parameter) return _resp( '0');
    if (!seen) proceed;
    if (now - seen <= CONSUMER_TTL_MS) lively.push(c);
  );

    const key = (e.parameter.key || '').toString();
    if (key !== API_KEY) return _resp( a < min) min = a;
  );

    const channel  = (e.parameter.channel || 'default').toString();
    const shopper = (e.parameter.shopper || '').toString();
    const c        = shopper || '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 mode = (e.parameter.mode || '').toString();
    if (mode === 'well being' || mode === 'debug') {
      const customers = _listActiveConsumersFast(retailer, channel);
      const minAck = _minAckFast(retailer, channel, customers);
      const out = ;

      if (mode === 'debug')  '0');
    retailer.setProperty(ackKey, String(Math.max(0, seq)));
    return;
  
      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) 
    const a = Quantity(retailer.getProperty(_ackKey(channel, c)) 

      strive  arr = JSON.parse(retailer.getProperty(listKey)  catch (parseErr) 
    }

    
    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) 
    const a = Quantity(retailer.getProperty(_ackKey(channel, c)) 
        strive { 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) {
      strive { lock.releaseLock(); } catch(_) {}
    }
  }
}



perform _nextSeq(retailer, channel) 





perform _touchConsumerFast(retailer, channel, shopper) {
  const now = Date.now();
  retailer.setProperty(_seenKey(channel, shopper), String(now));

  const listKey = _consumersKey(channel);
  let arr = ();
  strive  catch(_) { arr = (); }

  if (arr.indexOf(shopper) < 0) {
    arr.push(shopper);
    
    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, shopper);
  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));
  }
}

perform _listActiveConsumersFast(retailer, channel) {
  const now = Date.now();
  const listKey = _consumersKey(channel);

  let arr = ();
  strive  catch(_) { arr = (); }

  const lively = ();
  for (const c of arr) 

  if (lively.size === 0) lively.push('single');
  return lively;
}

perform _minAckFast(retailer, channel, customers) {
  let min = null;
  for (const c of customers)  a < min) min = a;
  
  return min === null ? 0 : min;
}

perform _pruneByMinAckFast(retailer, channel) {
  const customers = _listActiveConsumersFast(retailer, channel);
  const minAck = _minAckFast(retailer, channel, customers);
  if (minAck <= 0) return;

  _pruneAckedUpTo(retailer, channel, minAck);
}

perform _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));
  }
}


perform _seqKey(channel) { return channel + '__seq'; }
perform _minKey(channel) { return channel + '__min'; }
perform _ackKey(channel, shopper) { return channel + '__ack__' + shopper; }
perform _evKey(channel, id) { return channel + '__ev__' + id; }
perform _seenKey(channel, shopper) { return channel + '__seen__' + shopper; }
perform _consumersKey(channel) { return channel + '__consumers'; }

perform _resp(obj) {
  
  
  
  return ContentService
    .createTextOutput(JSON.stringify(obj))
    .setMimeType(ContentService.MimeType.JSON);
}



Supply hyperlink

Leave a Comment

Discover more from Education for All

Subscribe now to keep reading and get access to the full archive.

Continue reading