Thursday, February 19, 2026
Homeการซื้อขายวิธีการตั้งค่า Grid Copier ด้วย Google Apps Script (ทีละขั้นตอน) - อื่นๆ - 18...

วิธีการตั้งค่า Grid Copier ด้วย Google Apps Script (ทีละขั้นตอน) – อื่นๆ – 18 กุมภาพันธ์ 2569


เหตุใดเราจึงต้องมีสคริปต์ของ Google Apps

ในการแลกเปลี่ยนข้อมูลการค้าระหว่างเทอร์มินัล MetaTrader ที่แยกจากกัน จำเป็นต้องมีเซิร์ฟเวอร์รีเลย์แบบธรรมดา Google Apps Script ทำหน้าที่เป็นตัวกลางฟรีที่โอนกิจกรรมการค้าจากบัญชี Grasp ไปยังบัญชี Slave ช่วยให้มั่นใจได้ถึงการจัดกิจกรรมที่เชื่อถือได้ แม้ว่าอินเทอร์เน็ตจะหยุดชะงักหรือรีสตาร์ทเทอร์มินัล และไม่ต้องใช้ VPS หรือเซิร์ฟเวอร์เฉพาะ

วิธีสร้างและปรับใช้ Google Apps Script

  1. ไปที่ https://script.google.com และ คลิก เริ่มเขียนสคริปต์

  2. คลิก โครงการใหม่


  3. ลบรหัสเริ่มต้นและ วางสคริปต์ที่ให้ไว้ด้านล่าง ขั้นตอนการสอน


  4. กด Ctrl + ส เพื่อบันทึกโครงการ (เมนูด้านบนจะเปิดใช้งาน)


  5. คลิก ปรับใช้ ที่มุมขวาบนแล้วเลือก การใช้งานใหม่

  6. ในหน้าต่างที่เปิดอยู่ คลิกเลือกประเภท (⚙️)แล้วเลือก เว็บแอป


  7. ฉันไม่ใช่ คำอธิบาย สนาม, ป้อน เวอร์ชัน 1 (ข้อความใดก็ได้) ชุด ใครเข้าได้บ้าง ถึง ใครก็ได้ และจากไป ดำเนินการเป็น ไม่เปลี่ยนแปลง


  8. คลิก ปรับใช้ – ของคุณ URL สคริปต์ของแอป จะถูกสร้างขึ้น คัดลอกและวาง URL นี้ลงในการตั้งค่าอินพุตของ EA

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 {
    lock.waitLock(10000);
    locked = true;

    if (!e || !e.postData || !e.parameter) return _resp({ okay:false, error:'no information' });

    const key = (e.parameter.key || '').toString();
    if (key !== API_KEY) return _resp({ okay:false, error:'forbidden' });

    const channel  = (e.parameter.channel || 'default').toString();
    const shopper = (e.parameter.shopper || '').toString();
    const c        = shopper || 'single';

    const retailer = PropertiesService.getScriptProperties();

    
    let uncooked = e.postData.contents || '{}';
    uncooked = uncooked.substitute(/(u0000-u001F)+$/g, '');

    let physique;
    strive {
      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) {
      strive { lock.releaseLock(); } catch(_) {}
    }
  }
}

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

  strive {
    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 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 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, shopper: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;
      }

      strive {
        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; }
        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)  '0') + 1;
  retailer.setProperty(okay, String(subsequent));
  return subsequent;






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 === '')  '0');
    retailer.setProperty(ackKey, String(Math.max(0, seq)));
    return;
  

  
  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 energetic = ();
  for (const c of arr) 

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

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

perform _pruneByMinAckFast(retailer, channel) {
  const shoppers = _listActiveConsumersFast(retailer, channel);
  const minAck = _minAckFast(retailer, channel, shoppers);
  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);
}

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

ความเห็นล่าสุด